Compare commits

...

52 Commits

Author SHA1 Message Date
Bogdan
c05be39346 Treat unauthorized newbie accounts in AvistaZ parser 2024-10-12 22:21:05 +03:00
Bogdan
951d42a591 Fix indexer url info 2024-10-12 05:30:03 +03:00
Bogdan
dd046d8a68 Fixed: (Cardigann) Validate definition file and setting fields existence
Towards #2245
2024-10-11 19:23:30 +03:00
Bogdan
efa54a4d51 Remove unused gulp packages 2024-10-10 19:19:59 +03:00
Bogdan
3f07c50cc5 Fixed: Copy to clipboard in non-secure contexts
(cherry picked from commit 3828e475cc8860e74cdfd8a70b4f886de7f9c5c3)
2024-10-10 19:19:59 +03:00
Treycos
94cf07ddb4 Convert ClipboardButton to TypeScript
(cherry picked from commit 99fc52039f44264c83d939e5f096d8e16d2f3355)
2024-10-10 19:19:59 +03:00
Bogdan
24063e06ab Convert FormInputButton to TypeScript
(cherry picked from commit 32fa63d24d08d8d8877386a8d2e7065ab5d0ad39)
2024-10-10 19:19:59 +03:00
Treycos
e8ebb87189 Convert Label to TypeScript
(cherry picked from commit 3eca63a67c898256b711d37607f07cbabb9ed323)
2024-10-10 19:19:59 +03:00
Treycos
896e196767 Convert Button to TypeScript
(cherry picked from commit 63b4998c8e51d0d2b8b51133cbb1fd928394a7e6)
2024-10-10 19:19:59 +03:00
Bogdan
9f5be75e6d Link polymorphic static typing
(cherry picked from commit a2e06e9e650642518b926a61f624a2c7a49c0988)
(cherry picked from commit cfa2f4d4c6e35d7b9ddd2e1da2e59f7287859516)
2024-10-10 19:19:59 +03:00
Bogdan
9cc9e720bb Bump frontend packages 2024-10-10 19:19:59 +03:00
Bogdan
a9c2cca66d Bump dotnet packages 2024-10-09 23:56:11 +03:00
Bogdan
9cc3646be5 Fixed: (Cardigann) Using variables in login paths 2024-10-09 00:50:40 +03:00
Bogdan
d6bca449da Cleanse sharewood passkey 2024-10-09 00:26:08 +03:00
Bogdan
cb5764c654 Log exceptions when getting indexer definitions
Closes #2245
2024-10-08 01:44:36 +03:00
Weblate
19a9b56fa4 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Ardenet <1213193613@qq.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Kuzmich55 <kuzmich55@gmail.com>
Co-authored-by: Mathias <mathias@rodilbach.dk>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: angelsky11 <angelsky11@gmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: jsain <josip.sain@gmail.com>
Co-authored-by: liuwqq <843384478@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2024-10-06 18:19:32 +03:00
Tiago Santos
a2b0f199f1 Fixed: (BeyondHD) Filter freeleech or limited releases when configured 2024-10-06 17:56:21 +03:00
Mark McDowall
59bfad7614 New: Use 307 redirect for requests missing URL Base 2024-10-06 17:48:02 +03:00
Bogdan
aee3f2d12b Fixed: Handle 307 redirects from applications 2024-10-06 17:47:48 +03:00
Bogdan
11d58b4460 Bump macOS runner version to 13 2024-10-06 16:22:06 +03:00
Bogdan
ee4de6c6ca Bump version to 1.25.2 2024-10-06 12:04:50 +03:00
Bogdan
8d16b88185 Return bad request for unprotect download link failures 2024-10-05 17:20:17 +03:00
Bogdan
121ef8e80d Add new category for FL 2024-09-30 17:26:31 +03:00
Bogdan
d53fec7e75 Add newbie warning for AvistaZ's API use 2024-09-30 11:21:36 +03:00
Bogdan
c017a3cd7e New: (PTP) Filter by Golden Popcorn only releases 2024-09-29 12:12:26 +03:00
Bogdan
27ea93090f Use proxied requests for fetching user class for MAM 2024-09-29 10:40:16 +03:00
Bogdan
d79845144e Bump version to 1.25.1 2024-09-29 08:17:56 +03:00
Servarr
3f77900dd0 Automated API Docs update 2024-09-27 15:59:16 +03:00
Bogdan
4e8b9e81cf New: Option to prefer magnet URLs over torrent file links
Co-authored-by: Deathspike <meister.deathspike@outlook.com>

New: Bulk edit Prefer Magnet Url for indexers
2024-09-27 06:42:06 +03:00
Bogdan
a32ab3acfd Fixed: (AnimeBytes) Avoid specials for non-zero season searches 2024-09-27 06:24:04 +03:00
Bogdan
942da3a5c0 Bump version to 1.25.0 2024-09-27 06:23:48 +03:00
Qstick
17e1a72baf Bump webpack to 5.94.0 and regenerate yarn.lock 2024-09-22 22:34:45 -05:00
Bogdan
b454ded00a Bump version to 1.24.3 2024-09-22 07:48:53 +03:00
Servarr
d4512393e2 Automated API Docs update 2024-09-21 22:05:08 +03:00
Bogdan
97d1384726 Guard against using invalid sort keys 2024-09-21 21:35:23 +03:00
Bogdan
ba002a7a4a Add packages needed for RemoveDiacritics 2024-09-21 21:30:34 +03:00
momo
349efab7a8 Fix description for API key as query parameter
(cherry picked from commit 30c36fdc3baa686102ff124833c7963fc786f251)
2024-09-21 21:21:26 +03:00
Mark McDowall
af9a6f42db Fixed: Unable to login when instance name contained brackets 2024-09-21 00:27:15 +03:00
Mark McDowall
6b20fa8abd New: Use instance name in forms authentication cookie name
Closes #2224
2024-09-16 16:47:22 +03:00
Bogdan
029ad3903f Bump version to 1.24.2 2024-09-15 15:56:33 +03:00
Weblate
a23d66930b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: FloatStream <1213193613@qq.com>
Co-authored-by: Kuzmich55 <kuzmich55@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: shixiaotongy <shixiaotong2280@sina.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2024-09-14 17:14:52 +03:00
Bogdan
710ab7ae09 New: (Gazelle/OPS/RED) Prevent downloads without FL tokens 2024-09-08 15:19:25 +03:00
jaype87
434b07ae64 New: Sync seeding limits for LazyLibrarian (#2215)
* add support for seeders, seed_ratio and seed_duration for LazyLibrarian
2024-09-08 11:34:26 +03:00
Bogdan
eee8c95ca6 Fix weblate widget 2024-09-08 11:24:36 +03:00
Bogdan
1f5c514011 Bump version to 1.24.1 2024-09-08 11:10:36 +03:00
Weblate
66d722e097 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Dream <seth.gecko.rr@gmail.com>
Co-authored-by: FloatStream <1213193613@qq.com>
Co-authored-by: Gabriel Markowski <gmarkowski62@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Kerk en IT <info@kerkenit.nl>
Co-authored-by: MattiaPell <mattiapellegrini16@gmail.com>
Co-authored-by: Nota Inutilis <hugo@notainutilis.fr>
Co-authored-by: Weblate <noreply@weblate.org>
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/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2024-09-07 13:43:28 -05:00
Bogdan
39befe5aa4 Use error message from Nebulance response
Fixes #2212
2024-09-06 10:41:26 +03:00
Bogdan
ab043e87dc Display grabs, failures and queries stats with values 2024-09-04 16:28:06 +03:00
Bogdan
58ae9c0a13 Fixed: (MyAnonamouse) Avoid using FL wedges for freeleech torrents 2024-09-02 10:37:11 +03:00
Bogdan
44c446943c Fixed: (Gazelle) Allow freeleech torrents with Use Freeleech Tokens 2024-09-02 10:31:56 +03:00
Weblate
8301b669fe Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Dream <seth.gecko.rr@gmail.com>
Co-authored-by: Gabriel Markowski <gmarkowski62@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Kerk en IT <info@kerkenit.nl>
Co-authored-by: MattiaPell <mattiapellegrini16@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/es/
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/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translation: Servarr/Prowlarr
2024-09-01 21:21:40 -05:00
Bogdan
6fa0b79c67 Bump version to 1.24.0 2024-09-01 06:43:21 +03:00
96 changed files with 2992 additions and 2825 deletions

View File

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

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '1.23.1'
majorVersion: '1.25.2'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
@@ -20,7 +20,7 @@ variables:
innoVersion: '6.2.2'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04'
macImage: 'macOS-12'
macImage: 'macOS-13'
trigger:
branches:

View File

@@ -1,54 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import Button from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton';
import { kinds } from 'Helpers/Props';
import styles from './FormInputButton.css';
function FormInputButton(props) {
const {
className,
canSpin,
isLastButton,
...otherProps
} = props;
if (canSpin) {
return (
<SpinnerButton
className={classNames(
className,
!isLastButton && styles.middleButton
)}
kind={kinds.PRIMARY}
{...otherProps}
/>
);
}
return (
<Button
className={classNames(
className,
!isLastButton && styles.middleButton
)}
kind={kinds.PRIMARY}
{...otherProps}
/>
);
}
FormInputButton.propTypes = {
className: PropTypes.string.isRequired,
isLastButton: PropTypes.bool.isRequired,
canSpin: PropTypes.bool.isRequired
};
FormInputButton.defaultProps = {
className: styles.button,
isLastButton: true,
canSpin: false
};
export default FormInputButton;

View File

@@ -0,0 +1,38 @@
import classNames from 'classnames';
import React from 'react';
import Button, { ButtonProps } from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton';
import { kinds } from 'Helpers/Props';
import styles from './FormInputButton.css';
export interface FormInputButtonProps extends ButtonProps {
canSpin?: boolean;
isLastButton?: boolean;
}
function FormInputButton({
className = styles.button,
canSpin = false,
isLastButton = true,
...otherProps
}: FormInputButtonProps) {
if (canSpin) {
return (
<SpinnerButton
className={classNames(className, !isLastButton && styles.middleButton)}
kind={kinds.PRIMARY}
{...otherProps}
/>
);
}
return (
<Button
className={classNames(className, !isLastButton && styles.middleButton)}
kind={kinds.PRIMARY}
{...otherProps}
/>
);
}
export default FormInputButton;

View File

@@ -1,48 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import { kinds, sizes } from 'Helpers/Props';
import styles from './Label.css';
function Label(props) {
const {
className,
kind,
size,
outline,
children,
...otherProps
} = props;
return (
<span
className={classNames(
className,
styles[kind],
styles[size],
outline && styles.outline
)}
{...otherProps}
>
{children}
</span>
);
}
Label.propTypes = {
className: PropTypes.string.isRequired,
title: PropTypes.string,
kind: PropTypes.oneOf(kinds.all).isRequired,
size: PropTypes.oneOf(sizes.all).isRequired,
outline: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired
};
Label.defaultProps = {
className: styles.label,
kind: kinds.DEFAULT,
size: sizes.SMALL,
outline: false
};
export default Label;

View File

@@ -0,0 +1,33 @@
import classNames from 'classnames';
import React, { ComponentProps, ReactNode } from 'react';
import { kinds, sizes } from 'Helpers/Props';
import { Kind } from 'Helpers/Props/kinds';
import { Size } from 'Helpers/Props/sizes';
import styles from './Label.css';
export interface LabelProps extends ComponentProps<'span'> {
kind?: Extract<Kind, keyof typeof styles>;
size?: Extract<Size, keyof typeof styles>;
outline?: boolean;
children: ReactNode;
}
export default function Label({
className = styles.label,
kind = kinds.DEFAULT,
size = sizes.SMALL,
outline = false,
...otherProps
}: LabelProps) {
return (
<span
className={classNames(
className,
styles[kind],
styles[size],
outline && styles.outline
)}
{...otherProps}
/>
);
}

View File

@@ -1,54 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { align, kinds, sizes } from 'Helpers/Props';
import Link from './Link';
import styles from './Button.css';
class Button extends Component {
//
// Render
render() {
const {
className,
buttonGroupPosition,
kind,
size,
children,
...otherProps
} = this.props;
return (
<Link
className={classNames(
className,
styles[kind],
styles[size],
buttonGroupPosition && styles[buttonGroupPosition]
)}
{...otherProps}
>
{children}
</Link>
);
}
}
Button.propTypes = {
className: PropTypes.string.isRequired,
buttonGroupPosition: PropTypes.oneOf(align.all),
kind: PropTypes.oneOf(kinds.all),
size: PropTypes.oneOf(sizes.all),
children: PropTypes.node
};
Button.defaultProps = {
className: styles.button,
kind: kinds.DEFAULT,
size: sizes.MEDIUM
};
export default Button;

View File

@@ -0,0 +1,37 @@
import classNames from 'classnames';
import React from 'react';
import { align, kinds, sizes } from 'Helpers/Props';
import { Kind } from 'Helpers/Props/kinds';
import { Size } from 'Helpers/Props/sizes';
import Link, { LinkProps } from './Link';
import styles from './Button.css';
export interface ButtonProps extends Omit<LinkProps, 'children' | 'size'> {
buttonGroupPosition?: Extract<
(typeof align.all)[number],
keyof typeof styles
>;
kind?: Extract<Kind, keyof typeof styles>;
size?: Extract<Size, keyof typeof styles>;
children: Required<LinkProps['children']>;
}
export default function Button({
className = styles.button,
buttonGroupPosition,
kind = kinds.DEFAULT,
size = sizes.MEDIUM,
...otherProps
}: ButtonProps) {
return (
<Link
className={classNames(
className,
styles[kind],
styles[size],
buttonGroupPosition && styles[buttonGroupPosition]
)}
{...otherProps}
/>
);
}

View File

@@ -1,139 +0,0 @@
import Clipboard from 'clipboard';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FormInputButton from 'Components/Form/FormInputButton';
import Icon from 'Components/Icon';
import { icons, kinds } from 'Helpers/Props';
import getUniqueElememtId from 'Utilities/getUniqueElementId';
import styles from './ClipboardButton.css';
class ClipboardButton extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this._id = getUniqueElememtId();
this._successTimeout = null;
this._testResultTimeout = null;
this.state = {
showSuccess: false,
showError: false
};
}
componentDidMount() {
this._clipboard = new Clipboard(`#${this._id}`, {
text: () => this.props.value,
container: document.getElementById(this._id)
});
this._clipboard.on('success', this.onSuccess);
}
componentDidUpdate() {
const {
showSuccess,
showError
} = this.state;
if (showSuccess || showError) {
this._testResultTimeout = setTimeout(this.resetState, 3000);
}
}
componentWillUnmount() {
if (this._clipboard) {
this._clipboard.destroy();
}
if (this._testResultTimeout) {
clearTimeout(this._testResultTimeout);
}
}
//
// Control
resetState = () => {
this.setState({
showSuccess: false,
showError: false
});
};
//
// Listeners
onSuccess = () => {
this.setState({
showSuccess: true
});
};
onError = () => {
this.setState({
showError: true
});
};
//
// Render
render() {
const {
value,
className,
...otherProps
} = this.props;
const {
showSuccess,
showError
} = this.state;
const showStateIcon = showSuccess || showError;
const iconName = showError ? icons.DANGER : icons.CHECK;
const iconKind = showError ? kinds.DANGER : kinds.SUCCESS;
return (
<FormInputButton
id={this._id}
className={className}
{...otherProps}
>
<span className={showStateIcon ? styles.showStateIcon : undefined}>
{
showSuccess &&
<span className={styles.stateIconContainer}>
<Icon
name={iconName}
kind={iconKind}
/>
</span>
}
{
<span className={styles.clipboardIconContainer}>
<Icon name={icons.CLIPBOARD} />
</span>
}
</span>
</FormInputButton>
);
}
}
ClipboardButton.propTypes = {
className: PropTypes.string.isRequired,
value: PropTypes.string.isRequired
};
ClipboardButton.defaultProps = {
className: styles.button
};
export default ClipboardButton;

View File

@@ -0,0 +1,76 @@
import copy from 'copy-to-clipboard';
import React, { useCallback, useEffect, useState } from 'react';
import FormInputButton from 'Components/Form/FormInputButton';
import Icon from 'Components/Icon';
import { icons, kinds } from 'Helpers/Props';
import { ButtonProps } from './Button';
import styles from './ClipboardButton.css';
export interface ClipboardButtonProps extends Omit<ButtonProps, 'children'> {
value: string;
}
export type ClipboardState = 'success' | 'error' | null;
export default function ClipboardButton({
id,
value,
className = styles.button,
...otherProps
}: ClipboardButtonProps) {
const [state, setState] = useState<ClipboardState>(null);
useEffect(() => {
if (!state) {
return;
}
const timeoutId = setTimeout(() => {
setState(null);
}, 3000);
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}, [state]);
const handleClick = useCallback(async () => {
try {
if ('clipboard' in navigator) {
await navigator.clipboard.writeText(value);
} else {
copy(value);
}
setState('success');
} catch (e) {
setState('error');
console.error(`Failed to copy to clipboard`, e);
}
}, [value]);
return (
<FormInputButton
className={className}
onClick={handleClick}
{...otherProps}
>
<span className={state ? styles.showStateIcon : undefined}>
{state ? (
<span className={styles.stateIconContainer}>
<Icon
name={state === 'error' ? icons.DANGER : icons.CHECK}
kind={state === 'error' ? kinds.DANGER : kinds.SUCCESS}
/>
</span>
) : null}
<span className={styles.clipboardIconContainer}>
<Icon name={icons.CLIPBOARD} />
</span>
</span>
</FormInputButton>
);
}

View File

@@ -1,96 +1,93 @@
import classNames from 'classnames';
import React, {
ComponentClass,
FunctionComponent,
ComponentPropsWithoutRef,
ElementType,
SyntheticEvent,
useCallback,
} from 'react';
import { Link as RouterLink } from 'react-router-dom';
import styles from './Link.css';
interface ReactRouterLinkProps {
to?: string;
}
export type LinkProps<C extends ElementType = 'button'> =
ComponentPropsWithoutRef<C> & {
component?: C;
to?: string;
target?: string;
isDisabled?: LinkProps<C>['disabled'];
noRouter?: boolean;
onPress?(event: SyntheticEvent): void;
};
export interface LinkProps extends React.HTMLProps<HTMLAnchorElement> {
className?: string;
component?:
| string
| FunctionComponent<LinkProps>
| ComponentClass<LinkProps, unknown>;
to?: string;
target?: string;
isDisabled?: boolean;
noRouter?: boolean;
onPress?(event: SyntheticEvent): void;
}
function Link(props: LinkProps) {
const {
className,
component = 'button',
to,
target,
type,
isDisabled,
noRouter = false,
onPress,
...otherProps
} = props;
export default function Link<C extends ElementType = 'button'>({
className,
component,
to,
target,
type,
isDisabled,
noRouter,
onPress,
...otherProps
}: LinkProps<C>) {
const Component = component || 'button';
const onClick = useCallback(
(event: SyntheticEvent) => {
if (!isDisabled && onPress) {
onPress(event);
if (isDisabled) {
return;
}
onPress?.(event);
},
[isDisabled, onPress]
);
const linkProps: React.HTMLProps<HTMLAnchorElement> & ReactRouterLinkProps = {
target,
};
let el = component;
if (to) {
if (/\w+?:\/\//.test(to)) {
el = 'a';
linkProps.href = to;
linkProps.target = target || '_blank';
linkProps.rel = 'noreferrer';
} else if (noRouter) {
el = 'a';
linkProps.href = to;
linkProps.target = target || '_self';
} else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
el = RouterLink;
linkProps.to = `${window.Prowlarr.urlBase}/${to.replace(/^\//, '')}`;
linkProps.target = target;
}
}
if (el === 'button' || el === 'input') {
linkProps.type = type || 'button';
linkProps.disabled = isDisabled;
}
linkProps.className = classNames(
const linkClass = classNames(
className,
styles.link,
to && styles.to,
isDisabled && 'isDisabled'
);
const elementProps = {
...otherProps,
type,
...linkProps,
};
if (to) {
const toLink = /\w+?:\/\//.test(to);
elementProps.onClick = onClick;
if (toLink || noRouter) {
return (
<a
href={to}
target={target || (toLink ? '_blank' : '_self')}
rel={toLink ? 'noreferrer' : undefined}
className={linkClass}
onClick={onClick}
{...otherProps}
/>
);
}
return React.createElement(el, elementProps);
return (
<RouterLink
to={`${window.Prowlarr.urlBase}/${to.replace(/^\//, '')}`}
target={target}
className={linkClass}
onClick={onClick}
{...otherProps}
/>
);
}
return (
<Component
type={
component === 'button' || component === 'input'
? type || 'button'
: type
}
target={target}
className={linkClass}
disabled={isDisabled}
onClick={onClick}
{...otherProps}
/>
);
}
export default Link;

View File

@@ -7,7 +7,6 @@ export const PRIMARY = 'primary';
export const PURPLE = 'purple';
export const SUCCESS = 'success';
export const WARNING = 'warning';
export const QUEUE = 'queue';
export const all = [
DANGER,
@@ -19,5 +18,15 @@ export const all = [
PURPLE,
SUCCESS,
WARNING,
QUEUE
];
] as const;
export type Kind =
| 'danger'
| 'default'
| 'disabled'
| 'info'
| 'inverse'
| 'primary'
| 'purple'
| 'success'
| 'warning';

View File

@@ -4,4 +4,6 @@ export const MEDIUM = 'medium';
export const LARGE = 'large';
export const EXTRA_LARGE = 'extraLarge';
export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE];
export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE] as const;
export type Size = 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';

View File

@@ -19,6 +19,7 @@ interface SavePayload {
seedRatio?: number;
seedTime?: number;
packSeedTime?: number;
preferMagnetUrl?: boolean;
}
interface EditIndexerModalContentProps {
@@ -65,6 +66,9 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
const [packSeedTime, setPackSeedTime] = useState<null | string | number>(
null
);
const [preferMagnetUrl, setPreferMagnetUrl] = useState<
null | string | boolean
>(null);
const save = useCallback(() => {
let hasChanges = false;
@@ -105,6 +109,11 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
payload.packSeedTime = packSeedTime as number;
}
if (preferMagnetUrl !== null) {
hasChanges = true;
payload.preferMagnetUrl = preferMagnetUrl === 'true';
}
if (hasChanges) {
onSavePress(payload);
}
@@ -118,6 +127,7 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
seedRatio,
seedTime,
packSeedTime,
preferMagnetUrl,
onSavePress,
onModalClose,
]);
@@ -146,6 +156,9 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
case 'packSeedTime':
setPackSeedTime(value);
break;
case 'preferMagnetUrl':
setPreferMagnetUrl(value);
break;
default:
console.warn(`EditIndexersModalContent Unknown Input: '${name}'`);
}
@@ -254,6 +267,18 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
onChange={onInputChange}
/>
</FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('PreferMagnetUrl')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="preferMagnetUrl"
value={preferMagnetUrl}
values={enableOptions}
onChange={onInputChange}
/>
</FormGroup>
</ModalBody>
<ModalFooter className={styles.modalFooter}>

View File

@@ -29,7 +29,8 @@
.minimumSeeders,
.seedRatio,
.seedTime,
.packSeedTime {
.packSeedTime,
.preferMagnetUrl {
composes: cell;
flex: 0 0 90px;

View File

@@ -11,6 +11,7 @@ interface CssExports {
'id': string;
'minimumSeeders': string;
'packSeedTime': string;
'preferMagnetUrl': string;
'priority': string;
'privacy': string;
'protocol': string;

View File

@@ -1,6 +1,7 @@
import React, { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSelect } from 'App/SelectContext';
import CheckInput from 'Components/Form/CheckInput';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
@@ -74,6 +75,10 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
fields.find((field) => field.name === 'torrentBaseSettings.packSeedTime')
?.value ?? undefined;
const preferMagnetUrl =
fields.find((field) => field.name === 'torrentBaseSettings.preferMagnetUrl')
?.value ?? undefined;
const rssUrl = `${window.location.origin}${
window.Prowlarr.urlBase
}/${id}/api?apikey=${encodeURIComponent(
@@ -102,6 +107,10 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
setIsDeleteIndexerModalOpen(false);
}, [setIsDeleteIndexerModalOpen]);
const checkInputCallback = useCallback(() => {
// Mock handler to satisfy `onChange` being required for `CheckInput`.
}, []);
const onSelectedChange = useCallback(
({ id, value, shiftKey }: SelectStateInputProps) => {
selectDispatch({
@@ -277,6 +286,21 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
);
}
if (name === 'preferMagnetUrl') {
return (
<VirtualTableRowCell key={name} className={styles[name]}>
{preferMagnetUrl === undefined ? null : (
<CheckInput
name="preferMagnetUrl"
value={preferMagnetUrl}
isDisabled={true}
onChange={checkInputCallback}
/>
)}
</VirtualTableRowCell>
);
}
if (name === 'actions') {
return (
<VirtualTableRowCell

View File

@@ -22,7 +22,8 @@
.minimumSeeders,
.seedRatio,
.seedTime,
.packSeedTime {
.packSeedTime,
.preferMagnetUrl {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
flex: 0 0 90px;

View File

@@ -8,6 +8,7 @@ interface CssExports {
'id': string;
'minimumSeeders': string;
'packSeedTime': string;
'preferMagnetUrl': string;
'priority': string;
'privacy': string;
'protocol': string;

View File

@@ -1,8 +1,6 @@
import { uniqBy } from 'lodash';
import React, { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import { createSelector } from 'reselect';
import Alert from 'Components/Alert';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
@@ -26,23 +24,12 @@ import DeleteIndexerModal from 'Indexer/Delete/DeleteIndexerModal';
import EditIndexerModalConnector from 'Indexer/Edit/EditIndexerModalConnector';
import PrivacyLabel from 'Indexer/Index/Table/PrivacyLabel';
import Indexer, { IndexerCapabilities } from 'Indexer/Indexer';
import { createIndexerSelectorForHook } from 'Store/Selectors/createIndexerSelector';
import useIndexer from 'Indexer/useIndexer';
import translate from 'Utilities/String/translate';
import IndexerHistory from './History/IndexerHistory';
import styles from './IndexerInfoModalContent.css';
function createIndexerInfoItemSelector(indexerId: number) {
return createSelector(
createIndexerSelectorForHook(indexerId),
(indexer?: Indexer) => {
return {
indexer,
};
}
);
}
const tabs = ['details', 'categories', 'history', 'stats'];
const TABS = ['details', 'categories', 'history', 'stats'];
interface IndexerInfoModalContentProps {
indexerId: number;
@@ -51,9 +38,7 @@ interface IndexerInfoModalContentProps {
}
function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
const { indexerId, onCloneIndexerPress } = props;
const { indexer } = useSelector(createIndexerInfoItemSelector(indexerId));
const { indexerId, onModalClose, onCloneIndexerPress } = props;
const {
id,
@@ -67,53 +52,53 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
protocol,
privacy,
capabilities = {} as IndexerCapabilities,
} = indexer as Indexer;
} = useIndexer(indexerId) as Indexer;
const { onModalClose } = props;
const baseUrl =
fields.find((field) => field.name === 'baseUrl')?.value ??
(Array.isArray(indexerUrls) ? indexerUrls[0] : undefined);
const vipExpiration =
fields.find((field) => field.name === 'vipExpiration')?.value ?? undefined;
const [selectedTab, setSelectedTab] = useState(tabs[0]);
const [selectedTab, setSelectedTab] = useState(TABS[0]);
const [isEditIndexerModalOpen, setIsEditIndexerModalOpen] = useState(false);
const [isDeleteIndexerModalOpen, setIsDeleteIndexerModalOpen] =
useState(false);
const onTabSelect = useCallback(
(index: number) => {
const selectedTab = tabs[index];
const handleTabSelect = useCallback(
(selectedIndex: number) => {
const selectedTab = TABS[selectedIndex];
setSelectedTab(selectedTab);
},
[setSelectedTab]
);
const onEditIndexerPress = useCallback(() => {
const handleEditIndexerPress = useCallback(() => {
setIsEditIndexerModalOpen(true);
}, [setIsEditIndexerModalOpen]);
const onEditIndexerModalClose = useCallback(() => {
const handleEditIndexerModalClose = useCallback(() => {
setIsEditIndexerModalOpen(false);
}, [setIsEditIndexerModalOpen]);
const onDeleteIndexerPress = useCallback(() => {
const handleDeleteIndexerPress = useCallback(() => {
setIsEditIndexerModalOpen(false);
setIsDeleteIndexerModalOpen(true);
}, [setIsDeleteIndexerModalOpen]);
const onDeleteIndexerModalClose = useCallback(() => {
const handleDeleteIndexerModalClose = useCallback(() => {
setIsDeleteIndexerModalOpen(false);
onModalClose();
}, [setIsDeleteIndexerModalOpen, onModalClose]);
const onCloneIndexerPressWrapper = useCallback(() => {
const handleCloneIndexerPressWrapper = useCallback(() => {
onCloneIndexerPress(id);
onModalClose();
}, [id, onCloneIndexerPress, onModalClose]);
const baseUrl =
fields.find((field) => field.name === 'baseUrl')?.value ??
(Array.isArray(indexerUrls) ? indexerUrls[0] : undefined);
const indexerUrl = baseUrl?.replace(/(:\/\/)api\./, '$1');
const vipExpiration =
fields.find((field) => field.name === 'vipExpiration')?.value ?? undefined;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>{`${name}`}</ModalHeader>
@@ -121,8 +106,8 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
<ModalBody>
<Tabs
className={styles.tabs}
selectedIndex={tabs.indexOf(selectedTab)}
onSelect={onTabSelect}
selectedIndex={TABS.indexOf(selectedTab)}
onSelect={handleTabSelect}
>
<TabList className={styles.tabList}>
<Tab className={styles.tab} selectedClassName={styles.selectedTab}>
@@ -178,10 +163,8 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
{translate('IndexerSite')}
</DescriptionListItemTitle>
<DescriptionListItemDescription>
{baseUrl ? (
<Link to={baseUrl}>
{baseUrl.replace(/(:\/\/)api\./, '$1')}
</Link>
{indexerUrl ? (
<Link to={indexerUrl}>{indexerUrl}</Link>
) : (
'-'
)}
@@ -365,16 +348,16 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
onPress={onDeleteIndexerPress}
onPress={handleDeleteIndexerPress}
>
{translate('Delete')}
</Button>
<Button onPress={onCloneIndexerPressWrapper}>
<Button onPress={handleCloneIndexerPressWrapper}>
{translate('Clone')}
</Button>
</div>
<div>
<Button onPress={onEditIndexerPress}>{translate('Edit')}</Button>
<Button onPress={handleEditIndexerPress}>{translate('Edit')}</Button>
<Button onPress={onModalClose}>{translate('Close')}</Button>
</div>
</ModalFooter>
@@ -382,14 +365,14 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
<EditIndexerModalConnector
isOpen={isEditIndexerModalOpen}
id={id}
onModalClose={onEditIndexerModalClose}
onDeleteIndexerPress={onDeleteIndexerPress}
onModalClose={handleEditIndexerModalClose}
onDeleteIndexerPress={handleDeleteIndexerPress}
/>
<DeleteIndexerModal
isOpen={isDeleteIndexerModalOpen}
indexerId={id}
onModalClose={onDeleteIndexerModalClose}
onModalClose={handleDeleteIndexerModalClose}
/>
</ModalContent>
);

View File

@@ -54,18 +54,20 @@ function getAverageResponseTimeData(indexerStats: IndexerStatsIndexer[]) {
}
function getFailureRateData(indexerStats: IndexerStatsIndexer[]) {
const data = indexerStats.map((indexer) => ({
label: indexer.indexerName,
value:
(indexer.numberOfFailedQueries +
indexer.numberOfFailedRssQueries +
indexer.numberOfFailedAuthQueries +
indexer.numberOfFailedGrabs) /
(indexer.numberOfQueries +
indexer.numberOfRssQueries +
indexer.numberOfAuthQueries +
indexer.numberOfGrabs),
}));
const data = [...indexerStats]
.map((indexer) => ({
label: indexer.indexerName,
value:
(indexer.numberOfFailedQueries +
indexer.numberOfFailedRssQueries +
indexer.numberOfFailedAuthQueries +
indexer.numberOfFailedGrabs) /
(indexer.numberOfQueries +
indexer.numberOfRssQueries +
indexer.numberOfAuthQueries +
indexer.numberOfGrabs),
}))
.filter((s) => s.value > 0);
data.sort((a, b) => b.value - a.value);
@@ -73,13 +75,20 @@ function getFailureRateData(indexerStats: IndexerStatsIndexer[]) {
}
function getTotalRequestsData(indexerStats: IndexerStatsIndexer[]) {
const statistics = [...indexerStats].sort(
(a, b) =>
b.numberOfQueries +
b.numberOfRssQueries +
b.numberOfAuthQueries -
(a.numberOfQueries + a.numberOfRssQueries + a.numberOfAuthQueries)
);
const statistics = [...indexerStats]
.filter(
(s) =>
s.numberOfQueries > 0 ||
s.numberOfRssQueries > 0 ||
s.numberOfAuthQueries > 0
)
.sort(
(a, b) =>
b.numberOfQueries +
b.numberOfRssQueries +
b.numberOfAuthQueries -
(a.numberOfQueries + a.numberOfRssQueries + a.numberOfAuthQueries)
);
return {
labels: statistics.map((indexer) => indexer.indexerName),
@@ -101,10 +110,12 @@ function getTotalRequestsData(indexerStats: IndexerStatsIndexer[]) {
}
function getNumberGrabsData(indexerStats: IndexerStatsIndexer[]) {
const data = indexerStats.map((indexer) => ({
label: indexer.indexerName,
value: indexer.numberOfGrabs - indexer.numberOfFailedGrabs,
}));
const data = [...indexerStats]
.map((indexer) => ({
label: indexer.indexerName,
value: indexer.numberOfGrabs - indexer.numberOfFailedGrabs,
}))
.filter((s) => s.value > 0);
data.sort((a, b) => b.value - a.value);

View File

@@ -0,0 +1,19 @@
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
export function createIndexerSelector(indexerId?: number) {
return createSelector(
(state: AppState) => state.indexers.itemMap,
(state: AppState) => state.indexers.items,
(itemMap, allIndexers) => {
return indexerId ? allIndexers[itemMap[indexerId]] : undefined;
}
);
}
function useIndexer(indexerId?: number) {
return useSelector(createIndexerSelector(indexerId));
}
export default useIndexer;

View File

@@ -116,6 +116,26 @@ export const sortPredicates = {
vipExpiration: function({ fields = [] }) {
return fields.find((field) => field.name === 'vipExpiration')?.value ?? '';
},
minimumSeeders: function({ fields = [] }) {
return fields.find((field) => field.name === 'torrentBaseSettings.appMinimumSeeders')?.value ?? undefined;
},
seedRatio: function({ fields = [] }) {
return fields.find((field) => field.name === 'torrentBaseSettings.seedRatio')?.value ?? undefined;
},
seedTime: function({ fields = [] }) {
return fields.find((field) => field.name === 'torrentBaseSettings.seedTime')?.value ?? undefined;
},
packSeedTime: function({ fields = [] }) {
return fields.find((field) => field.name === 'torrentBaseSettings.packSeedTime')?.value ?? undefined;
},
preferMagnetUrl: function({ fields = [] }) {
return fields.find((field) => field.name === 'torrentBaseSettings.preferMagnetUrl')?.value ?? undefined;
}
};

View File

@@ -116,6 +116,12 @@ export const defaultState = {
isSortable: true,
isVisible: false
},
{
name: 'preferMagnetUrl',
label: () => translate('PreferMagnetUrl'),
isSortable: true,
isVisible: false
},
{
name: 'tags',
label: () => translate('Tags'),

View File

@@ -110,7 +110,6 @@ export const defaultState = {
{
name: 'actions',
columnLabel: () => translate('Actions'),
isSortable: true,
isVisible: true,
isModifiable: false
}

View File

@@ -1,7 +1,9 @@
let i = 0;
// returns a HTML 4.0 compliant element IDs (http://stackoverflow.com/a/79022)
/**
* @deprecated Use React's useId() instead
* @returns An HTML 4.0 compliant element IDs (http://stackoverflow.com/a/79022)
*/
export default function getUniqueElementId() {
return `id-${i++}`;
}

View File

@@ -30,28 +30,27 @@
"@fortawesome/react-fontawesome": "0.2.2",
"@juggle/resize-observer": "3.4.0",
"@microsoft/signalr": "6.0.25",
"@sentry/browser": "7.100.0",
"@sentry/integrations": "7.100.0",
"@types/node": "18.19.31",
"@sentry/browser": "7.119.1",
"@sentry/integrations": "7.119.1",
"@types/node": "20.16.11",
"@types/react": "18.2.79",
"@types/react-dom": "18.2.25",
"chart.js": "4.4.3",
"classnames": "2.3.2",
"clipboard": "2.0.11",
"chart.js": "4.4.4",
"classnames": "2.5.1",
"connected-react-router": "6.9.3",
"copy-to-clipboard": "3.3.3",
"element-class": "0.2.2",
"filesize": "10.0.7",
"filesize": "10.1.6",
"history": "4.10.1",
"https-browserify": "1.0.0",
"jdu": "1.0.0",
"jquery": "3.7.1",
"lodash": "4.17.21",
"mobile-detect": "1.4.5",
"moment": "2.29.4",
"moment": "2.30.1",
"mousetrap": "1.6.5",
"normalize.css": "8.0.1",
"prop-types": "15.8.1",
"qs": "6.11.1",
"qs": "6.13.0",
"react": "17.0.2",
"react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0",
@@ -65,7 +64,6 @@
"react-dom": "17.0.2",
"react-focus-lock": "2.9.4",
"react-google-recaptcha": "2.1.0",
"react-lazyload": "3.2.0",
"react-measure": "1.4.7",
"react-popper": "1.3.7",
"react-redux": "7.2.4",
@@ -75,55 +73,55 @@
"react-text-truncate": "0.19.0",
"react-use-measure": "2.1.1",
"react-virtualized": "9.21.1",
"react-window": "1.8.8",
"react-window": "1.8.10",
"redux": "4.2.1",
"redux-actions": "2.6.5",
"redux-batched-actions": "0.5.0",
"redux-localstorage": "0.4.1",
"redux-thunk": "2.4.2",
"reselect": "4.1.7",
"reselect": "4.1.8",
"stacktrace-js": "2.0.2",
"typescript": "5.1.6"
},
"devDependencies": {
"@babel/core": "7.25.2",
"@babel/eslint-parser": "7.25.1",
"@babel/plugin-proposal-export-default-from": "7.24.7",
"@babel/core": "7.25.8",
"@babel/eslint-parser": "7.25.8",
"@babel/plugin-proposal-export-default-from": "7.25.8",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.25.3",
"@babel/preset-react": "7.24.7",
"@babel/preset-typescript": "7.24.7",
"@types/lodash": "4.14.194",
"@types/react-document-title": "2.0.9",
"@babel/preset-env": "7.25.8",
"@babel/preset-react": "7.25.7",
"@babel/preset-typescript": "7.25.7",
"@types/lodash": "4.14.195",
"@types/react-document-title": "2.0.10",
"@types/react-router-dom": "5.3.3",
"@types/react-text-truncate": "0.14.1",
"@types/react-window": "1.8.5",
"@types/webpack-livereload-plugin": "2.3.3",
"@types/react-text-truncate": "0.19.0",
"@types/react-window": "1.8.8",
"@types/webpack-livereload-plugin": "2.3.6",
"@typescript-eslint/eslint-plugin": "6.21.0",
"@typescript-eslint/parser": "6.21.0",
"are-you-es5": "2.1.2",
"autoprefixer": "10.4.20",
"babel-loader": "9.1.3",
"babel-loader": "9.2.1",
"babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"core-js": "3.38.0",
"core-js": "3.38.1",
"css-loader": "6.7.3",
"css-modules-typescript-loader": "4.0.1",
"eslint": "8.57.0",
"eslint": "8.57.1",
"eslint-config-prettier": "8.10.0",
"eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.34.1",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-simple-import-sort": "12.1.0",
"eslint-plugin-react": "7.37.1",
"eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-simple-import-sort": "12.1.1",
"file-loader": "6.2.0",
"filemanager-webpack-plugin": "8.0.0",
"fork-ts-checker-webpack-plugin": "8.0.0",
"html-webpack-plugin": "5.5.1",
"html-webpack-plugin": "5.6.0",
"loader-utils": "^3.2.1",
"mini-css-extract-plugin": "2.7.5",
"postcss": "8.4.41",
"mini-css-extract-plugin": "2.9.1",
"postcss": "8.4.47",
"postcss-color-function": "4.1.0",
"postcss-loader": "7.3.0",
"postcss-mixins": "9.0.4",
@@ -132,17 +130,15 @@
"postcss-url": "10.1.3",
"prettier": "2.8.8",
"require-nocache": "1.0.0",
"rimraf": "4.4.1",
"run-sequence": "2.2.1",
"streamqueue": "1.1.2",
"rimraf": "6.0.1",
"style-loader": "3.3.2",
"stylelint": "15.6.1",
"stylelint-order": "6.0.3",
"terser-webpack-plugin": "5.3.9",
"ts-loader": "9.4.2",
"stylelint-order": "6.0.4",
"terser-webpack-plugin": "5.3.10",
"ts-loader": "9.5.1",
"typescript-plugin-css-modules": "5.0.1",
"url-loader": "4.1.1",
"webpack": "5.89.0",
"webpack": "5.95.0",
"webpack-cli": "5.1.4",
"webpack-livereload-plugin": "3.0.2"
}

View File

@@ -29,6 +29,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://beyond-hd.me/torrent/download/the-next-365-days-2022-2160p-nf-web-dl-dual-ddp-51-dovi-hdr-hevc-apex.225146.2b51db35e1912ffc138825a12b9933d2")]
[TestCase(@"https://anthelion.me/api.php?api_key=2b51db35e1910123321025a12b9933d2&o=json&t=movie&q=&tmdb=&imdb=&cat=&limit=100&offset=0")]
[TestCase(@"https://avistaz.to/api/v1/jackett/auth: username=mySecret&password=mySecret&pid=mySecret")]
[TestCase(@"https://www.sharewood.tv/api/2b51db35e1910123321025a12b9933d2/last-torrents")]
// Indexer and Download Client Responses

View File

@@ -1,4 +1,5 @@
using System;
using System.Web;
namespace NzbDrone.Common.Extensions
{
@@ -18,5 +19,24 @@ namespace NzbDrone.Common.Extensions
return Uri.TryCreate(path, UriKind.Absolute, out var uri) && uri.IsWellFormedOriginalString();
}
public static Uri RemoveQueryParam(this Uri url, string name)
{
var uriBuilder = new UriBuilder(url);
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
query.Remove(name);
uriBuilder.Query = query.ToString() ?? string.Empty;
return uriBuilder.Uri;
}
public static string GetQueryParam(this Uri url, string name)
{
var uriBuilder = new UriBuilder(url);
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
return query[name];
}
}
}

View File

@@ -21,6 +21,7 @@ namespace NzbDrone.Common.Instrumentation
new (@"(?<=authkey = "")(?<secret>[^&=]+?)(?="")", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"(?<=beyond-hd\.[a-z]+/api/torrents/)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"(?<=beyond-hd\.[a-z]+/torrent/download/[\w\d-]+[.]\d+[.])(?<secret>[a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"(?:sharewood)\.[a-z]{2,3}/api/(?<secret>[a-z0-9]{16,})/", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// UNIT3D
new (@"(?<=[a-z0-9-]+\.[a-z]+/torrent/download/\d+\.)(?<secret>[^&=][a-z0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),

View File

@@ -12,7 +12,7 @@
<PackageReference Include="NLog" Version="5.3.3" />
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.12" />
<PackageReference Include="Npgsql" Version="7.0.7" />
<PackageReference Include="Npgsql" Version="7.0.8" />
<PackageReference Include="Sentry" Version="4.0.2" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />

View File

@@ -165,6 +165,13 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
Priority = indexer.Priority
};
if (indexer.Protocol == DownloadProtocol.Torrent)
{
lazyLibrarianIndexer.MinimumSeeders = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
lazyLibrarianIndexer.SeedRatio = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio.GetValueOrDefault();
lazyLibrarianIndexer.SeedTime = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime.GetValueOrDefault();
}
return lazyLibrarianIndexer;
}
}

View File

@@ -31,6 +31,9 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
public string Altername { get; set; }
public LazyLibrarianProviderType Type { get; set; }
public int Priority { get; set; }
public double SeedRatio { get; set; }
public int SeedTime { get; set; }
public int MinimumSeeders { get; set; }
public bool Equals(LazyLibrarianIndexer other)
{
@@ -45,7 +48,10 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
other.Categories == Categories &&
other.Enabled == Enabled &&
other.Altername == Altername &&
other.Priority == Priority;
other.Priority == Priority &&
other.SeedRatio == SeedRatio &&
other.SeedTime == SeedTime &&
other.MinimumSeeders == MinimumSeeders;
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using FluentValidation.Results;
@@ -96,6 +97,13 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
{ "dlpriority", CalculatePriority(indexer.Priority).ToString() }
};
if (indexer.Type == LazyLibrarianProviderType.Torznab)
{
parameters.Add("seeders", indexer.MinimumSeeders.ToString());
parameters.Add("seed_ratio", indexer.SeedRatio.ToString(CultureInfo.InvariantCulture));
parameters.Add("seed_duration", indexer.SeedTime.ToString());
}
var request = BuildRequest(settings, "/api", "addProvider", HttpMethod.Get, parameters);
CheckForError(Execute<LazyLibrarianStatus>(request));
return indexer;
@@ -115,6 +123,13 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
{ "dlpriority", CalculatePriority(indexer.Priority).ToString() }
};
if (indexer.Type == LazyLibrarianProviderType.Torznab)
{
parameters.Add("seeders", indexer.MinimumSeeders.ToString());
parameters.Add("seed_ratio", indexer.SeedRatio.ToString(CultureInfo.InvariantCulture));
parameters.Add("seed_duration", indexer.SeedTime.ToString());
}
var request = BuildRequest(settings, "/api", "changeProvider", HttpMethod.Get, parameters);
CheckForError(Execute<LazyLibrarianStatus>(request));
return indexer;

View File

@@ -64,6 +64,7 @@ namespace NzbDrone.Core.Applications.Lidarr
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Lidarr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "Lidarr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Lidarr URL is invalid, Prowlarr cannot connect to Lidarr - are you missing a URL base?"));
break;

View File

@@ -166,6 +166,7 @@ namespace NzbDrone.Core.Applications.Lidarr
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:

View File

@@ -64,6 +64,7 @@ namespace NzbDrone.Core.Applications.Radarr
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Radarr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "Radarr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Radarr URL is invalid, Prowlarr cannot connect to Radarr - are you missing a URL base?"));
break;

View File

@@ -179,6 +179,7 @@ namespace NzbDrone.Core.Applications.Radarr
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:

View File

@@ -64,6 +64,7 @@ namespace NzbDrone.Core.Applications.Readarr
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Readarr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "Readarr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Readarr URL is invalid, Prowlarr cannot connect to Readarr - are you missing a URL base?"));
break;

View File

@@ -153,6 +153,7 @@ namespace NzbDrone.Core.Applications.Readarr
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:

View File

@@ -64,6 +64,7 @@ namespace NzbDrone.Core.Applications.Sonarr
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Sonarr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "Sonarr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Sonarr URL is invalid, Prowlarr cannot connect to Sonarr - are you missing a URL base?"));
break;

View File

@@ -166,6 +166,7 @@ namespace NzbDrone.Core.Applications.Sonarr
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:

View File

@@ -64,6 +64,7 @@ namespace NzbDrone.Core.Applications.Whisparr
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Whisparr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "Whisparr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Whisparr URL is invalid, Prowlarr cannot connect to Whisparr - are you missing a URL base?"));
break;

View File

@@ -151,6 +151,7 @@ namespace NzbDrone.Core.Applications.Whisparr
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
case HttpStatusCode.TemporaryRedirect:
_logger.Warn(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:

View File

@@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Data;
using Dapper;
using FluentMigrator;
using Newtonsoft.Json.Linq;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(041)]
public class gazelle_freeleech_token_options : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(MigrateIndexersToTokenOptions);
}
private void MigrateIndexersToTokenOptions(IDbConnection conn, IDbTransaction tran)
{
var updated = new List<object>();
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandText = "SELECT \"Id\", \"Settings\" FROM \"Indexers\" WHERE \"Implementation\" IN ('Orpheus', 'Redacted', 'AlphaRatio', 'BrokenStones', 'CGPeers', 'DICMusic', 'GreatPosterWall', 'SecretCinema')";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var id = reader.GetInt32(0);
var settings = Json.Deserialize<JObject>(reader.GetString(1));
if (settings.ContainsKey("useFreeleechToken") && settings.Value<JToken>("useFreeleechToken").Type == JTokenType.Boolean)
{
var optionValue = settings.Value<bool>("useFreeleechToken") switch
{
true => 2, // Required
_ => 0 // Never
};
settings.Remove("useFreeleechToken");
settings.Add("useFreeleechToken", optionValue);
}
updated.Add(new
{
Id = id,
Settings = settings.ToJson()
});
}
}
}
var updateSql = "UPDATE \"Indexers\" SET \"Settings\" = @Settings WHERE \"Id\" = @Id";
conn.Execute(updateSql, updated, transaction: tran);
}
}
}

View File

@@ -54,7 +54,7 @@ namespace NzbDrone.Core.IndexerSearch
return new XElement(feedNamespace + "attr", new XAttribute("name", name), new XAttribute("value", value));
}
public string ToXml(DownloadProtocol protocol)
public string ToXml(DownloadProtocol protocol, bool preferMagnetUrl = false)
{
// IMPORTANT: We can't use Uri.ToString(), because it generates URLs without URL encode (links with unicode
// characters are broken). We must use Uri.AbsoluteUri instead that handles encoding correctly
@@ -73,6 +73,7 @@ namespace NzbDrone.Core.IndexerSearch
new XElement("title", "Prowlarr"),
from r in Releases
let t = (r as TorrentInfo) ?? new TorrentInfo()
let downloadUrl = preferMagnetUrl ? t.MagnetUrl ?? r.DownloadUrl : r.DownloadUrl ?? t.MagnetUrl
select new XElement("item",
new XElement("title", RemoveInvalidXMLChars(r.Title)),
new XElement("description", RemoveInvalidXMLChars(r.Description)),
@@ -85,11 +86,11 @@ namespace NzbDrone.Core.IndexerSearch
r.InfoUrl == null ? null : new XElement("comments", r.InfoUrl),
r.PublishDate == DateTime.MinValue ? new XElement("pubDate", XmlDateFormat(DateTime.Now)) : new XElement("pubDate", XmlDateFormat(r.PublishDate)),
new XElement("size", r.Size),
new XElement("link", r.DownloadUrl ?? t.MagnetUrl ?? string.Empty),
new XElement("link", downloadUrl ?? string.Empty),
r.Categories == null ? null : from c in r.Categories select new XElement("category", c.Id),
new XElement(
"enclosure",
new XAttribute("url", r.DownloadUrl ?? t.MagnetUrl ?? string.Empty),
new XAttribute("url", downloadUrl ?? string.Empty),
r.Size == null ? null : new XAttribute("length", r.Size),
new XAttribute("type", protocol == DownloadProtocol.Torrent ? "application/x-bittorrent" : "application/x-nzb")),
r.Categories == null ? null : from c in r.Categories select GetNabElement("category", c.Id, protocol),

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
@@ -94,8 +95,10 @@ namespace NzbDrone.Core.IndexerVersions
var response = _httpClient.Get<List<CardigannMetaDefinition>>(request);
indexerList = response.Resource.Where(i => !_definitionBlocklist.Contains(i.File)).ToList();
}
catch
catch (Exception ex)
{
_logger.Warn(ex, "Error while getting indexer definitions, fallback to reading from disk.");
var definitionFolder = Path.Combine(_appFolderInfo.AppDataFolder, "Definitions");
indexerList = ReadDefinitionsFromDisk(indexerList, definitionFolder);
@@ -106,9 +109,9 @@ namespace NzbDrone.Core.IndexerVersions
indexerList = ReadDefinitionsFromDisk(indexerList, customDefinitionFolder);
}
catch
catch (Exception ex)
{
_logger.Error("Failed to Connect to Indexer Definition Server for Indexer listing");
_logger.Error(ex, "Failed to Connect to Indexer Definition Server for Indexer listing");
}
return indexerList;
@@ -116,7 +119,7 @@ namespace NzbDrone.Core.IndexerVersions
public CardigannDefinition GetCachedDefinition(string fileKey)
{
if (string.IsNullOrEmpty(fileKey))
if (string.IsNullOrWhiteSpace(fileKey))
{
throw new ArgumentNullException(nameof(fileKey));
}
@@ -172,7 +175,7 @@ namespace NzbDrone.Core.IndexerVersions
private CardigannDefinition GetUncachedDefinition(string fileKey)
{
if (string.IsNullOrEmpty(fileKey))
if (string.IsNullOrWhiteSpace(fileKey))
{
throw new ArgumentNullException(nameof(fileKey));
}
@@ -220,9 +223,24 @@ namespace NzbDrone.Core.IndexerVersions
private CardigannDefinition GetHttpDefinition(string id)
{
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);
if (string.IsNullOrWhiteSpace(id))
{
throw new ArgumentNullException(nameof(id));
}
CardigannDefinition definition;
try
{
var request = new HttpRequest($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}/{id}");
var response = _httpClient.Get(request);
definition = _deserializer.Deserialize<CardigannDefinition>(response.Content);
}
catch (HttpException ex) when (ex.Response.StatusCode == HttpStatusCode.NotFound)
{
throw new Exception($"Indexer definition for '{id}' does not exist.", ex);
}
return CleanIndexerDefinition(definition);
}

View File

@@ -143,7 +143,7 @@ public class AlphaRatioParser : GazelleParser
.AddQueryParam("action", "download")
.AddQueryParam("id", torrentId);
if (Settings.UseFreeleechToken && canUseToken)
if (Settings.UseFreeleechToken is (int)GazelleFreeleechTokenAction.Preferred or (int)GazelleFreeleechTokenAction.Required && canUseToken)
{
url = url.AddQueryParam("usetoken", "1");
}

View File

@@ -227,7 +227,13 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories).Distinct().ToList();
if (queryCats.Any() && searchCriteria is TvSearchCriteria { Season: > 0 })
{
// Avoid searching for specials if it's a non-zero season search
queryCats.RemoveAll(cat => cat is "anime[tv_special]" or "anime[ova]" or "anime[ona]" or "anime[dvd_special]" or "anime[bd_special]");
}
if (queryCats.Any())
{
@@ -246,9 +252,7 @@ namespace NzbDrone.Core.Indexers.Definitions
searchUrl += "?" + parameters.GetQueryString();
var request = new IndexerRequest(searchUrl, HttpAccept.Json);
yield return request;
yield return new IndexerRequest(searchUrl, HttpAccept.Json);
}
private static string CleanSearchTerm(string term)

View File

@@ -33,6 +33,12 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
throw new RequestLimitReachedException(indexerResponse, "API Request Limit Reached");
}
if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.Unauthorized)
{
STJson.TryDeserialize<AvistazErrorResponse>(indexerResponse.HttpResponse.Content, out var errorResponse);
throw new IndexerAuthException(errorResponse?.Message ?? "Unauthorized request to indexer");
}
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request");

View File

@@ -27,16 +27,16 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public string Token { get; set; }
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
[FieldDefinition(2, Label = "Username", HelpText = "IndexerAvistazSettingsUsernameHelpText", HelpTextWarning = "IndexerAvistazSettingsUsernameHelpTextWarning", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
[FieldDefinition(3, Label = "Password", HelpText = "IndexerAvistazSettingsPasswordHelpText", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
public string Password { get; set; }
[FieldDefinition(4, Label = "PID", HelpText = "PID from My Account or My Profile page", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
[FieldDefinition(4, Label = "PID", HelpText = "IndexerAvistazSettingsPidHelpText", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
public string Pid { get; set; }
[FieldDefinition(5, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search freeleech only")]
[FieldDefinition(5, Label = "IndexerSettingsFreeleechOnly", Type = FieldType.Checkbox, HelpText = "IndexerAvistazSettingsFreeleechOnlyHelpText")]
public bool FreeleechOnly { get; set; }
public override NzbDroneValidationResult Validate()

View File

@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IParseIndexerResponse GetParser()
{
return new BeyondHDParser(Capabilities.Categories);
return new BeyondHDParser(Settings, Capabilities.Categories);
}
protected override IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> releases, SearchCriteriaBase searchCriteria)
@@ -227,10 +227,12 @@ namespace NzbDrone.Core.Indexers.Definitions
public class BeyondHDParser : IParseIndexerResponse
{
private readonly BeyondHDSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public BeyondHDParser(IndexerCapabilitiesCategories categories)
public BeyondHDParser(BeyondHDSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
@@ -264,6 +266,12 @@ namespace NzbDrone.Core.Indexers.Definitions
foreach (var row in jsonResponse.Results)
{
// Skip invalid results when freeleech or limited filtering is set
if ((_settings.FreeleechOnly && !row.Freeleech) || (_settings.LimitedOnly && !row.Limited))
{
continue;
}
var details = row.InfoUrl;
var link = row.DownloadLink;

View File

@@ -212,7 +212,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
}
}
var loginUrl = ResolvePath(login.Path).ToString();
var loginUrl = ResolvePath(ApplyGoTemplateText(login.Path, variables)).ToString();
CookiesUpdater(null, null);
@@ -253,7 +253,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
}
else if (login.Method == "form")
{
var loginUrl = ResolvePath(login.Path).ToString();
var loginUrl = ResolvePath(ApplyGoTemplateText(login.Path, variables)).ToString();
var queryCollection = new NameValueCollection();
var pairs = new Dictionary<string, string>();
@@ -534,7 +534,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
}
}
var loginUrl = ResolvePath(login.Path + "?" + queryCollection.GetQueryString()).ToString();
var loginUrl = ResolvePath(ApplyGoTemplateText(login.Path, variables) + "?" + queryCollection.GetQueryString()).ToString();
CookiesUpdater(null, null);
@@ -563,7 +563,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
else if (login.Method == "oneurl")
{
var oneUrl = ApplyGoTemplateText(login.Inputs["oneurl"]);
var loginUrl = ResolvePath(login.Path + oneUrl).ToString();
var loginUrl = ResolvePath(ApplyGoTemplateText(login.Path, variables) + oneUrl).ToString();
CookiesUpdater(null, null);
@@ -639,7 +639,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
var variables = GetBaseTemplateVariables();
var headers = ParseCustomHeaders(_definition.Login?.Headers ?? _definition.Search?.Headers, variables);
var loginUrl = ResolvePath(login.Path);
var loginUrl = ResolvePath(ApplyGoTemplateText(login.Path, variables));
var requestBuilder = new HttpRequestBuilder(loginUrl.AbsoluteUri)
{
@@ -700,7 +700,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
var captchaElement = landingResultDocument.QuerySelector(captcha.Selector);
if (captchaElement != null)
{
var loginUrl = ResolvePath(login.Path);
var loginUrl = ResolvePath(ApplyGoTemplateText(login.Path, variables));
var captchaUrl = ResolvePath(captchaElement.GetAttribute("src"), loginUrl);
var request = new HttpRequestBuilder(captchaUrl.ToString())

View File

@@ -1,11 +1,23 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{
public class CardigannSettingsValidator : NoAuthSettingsValidator<CardigannSettings>
{
public CardigannSettingsValidator()
{
RuleFor(c => c.DefinitionFile).NotEmpty();
}
}
public class CardigannSettings : NoAuthTorrentBaseSettings
{
private static readonly CardigannSettingsValidator Validator = new ();
public CardigannSettings()
{
ExtraFieldData = new Dictionary<string, object>();
@@ -15,5 +27,10 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
public string DefinitionFile { get; set; }
public Dictionary<string, object> ExtraFieldData { get; set; }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -103,6 +103,8 @@ public class FileList : TorrentIndexerBase<FileListSettings>
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.Movies3D, "Filme 3D");
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.MoviesBluRay, "Filme 4K Blu-Ray");
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.TVUHD, "Seriale 4K");
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.MoviesForeign, "RO Dubbed");
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.TVForeign, "RO Dubbed");
return caps;
}

View File

@@ -151,7 +151,7 @@ public class FileListRequestGenerator : IIndexerRequestGenerator
if (searchCriteria.Categories != null && searchCriteria.Categories.Any())
{
parameters.Set("category", string.Join(",", Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories)));
parameters.Set("category", string.Join(",", Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories).Distinct().ToList()));
}
if (Settings.FreeleechOnly)

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Messaging.Events;
@@ -85,22 +86,23 @@ public abstract class GazelleBase<TSettings> : TorrentIndexerBase<TSettings>
public override async Task<IndexerDownloadResponse> Download(Uri link)
{
var downloadResponse = await base.Download(link);
var response = downloadResponse.Data;
if (response.Length >= 1
&& response[0] != 'd' // simple test for torrent vs HTML content
var fileData = downloadResponse.Data;
if (Settings.UseFreeleechToken == (int)GazelleFreeleechTokenAction.Preferred
&& fileData.Length >= 1
&& fileData[0] != 'd' // simple test for torrent vs HTML content
&& link.Query.Contains("usetoken=1"))
{
var html = Encoding.GetString(response);
var html = Encoding.GetString(fileData);
if (html.Contains("You do not have any freeleech tokens left.")
|| html.Contains("You do not have enough freeleech tokens")
|| html.Contains("This torrent is too large.")
|| html.Contains("You cannot use tokens here"))
{
// download again without usetoken=1
var requestLinkNew = link.ToString().Replace("&usetoken=1", "");
downloadResponse = await base.Download(new Uri(requestLinkNew));
// Try to download again without usetoken
downloadResponse = await base.Download(link.RemoveQueryParam("usetoken"));
}
}

View File

@@ -59,8 +59,10 @@ public class GazelleParser : IParseIndexerResponse
{
foreach (var torrent in result.Torrents)
{
var isFreeLeech = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsPersonalFreeLeech;
// skip releases that cannot be used with freeleech tokens when the option is enabled
if (Settings.UseFreeleechToken && !torrent.CanUseToken)
if (Settings.UseFreeleechToken == (int)GazelleFreeleechTokenAction.Required && !torrent.CanUseToken && !isFreeLeech)
{
continue;
}
@@ -79,7 +81,7 @@ public class GazelleParser : IParseIndexerResponse
{
Guid = infoUrl,
InfoUrl = infoUrl,
DownloadUrl = GetDownloadUrl(id, !torrent.IsFreeLeech && !torrent.IsNeutralLeech && !torrent.IsPersonalFreeLeech),
DownloadUrl = GetDownloadUrl(id, torrent.CanUseToken && !isFreeLeech),
Title = WebUtility.HtmlDecode(title),
Container = torrent.Encoding,
Files = torrent.FileCount,
@@ -91,7 +93,7 @@ public class GazelleParser : IParseIndexerResponse
PublishDate = torrent.Time.ToUniversalTime(),
Scene = torrent.Scene,
PosterUrl = posterUrl,
DownloadVolumeFactor = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsPersonalFreeLeech ? 0 : 1,
DownloadVolumeFactor = isFreeLeech ? 0 : 1,
UploadVolumeFactor = torrent.IsNeutralLeech ? 0 : 1
};
@@ -110,8 +112,10 @@ public class GazelleParser : IParseIndexerResponse
}
else
{
var isFreeLeech = result.IsFreeLeech || result.IsNeutralLeech || result.IsPersonalFreeLeech;
// skip releases that cannot be used with freeleech tokens when the option is enabled
if (Settings.UseFreeleechToken && !result.CanUseToken)
if (Settings.UseFreeleechToken == (int)GazelleFreeleechTokenAction.Required && !result.CanUseToken && !isFreeLeech)
{
continue;
}
@@ -124,7 +128,7 @@ public class GazelleParser : IParseIndexerResponse
{
Guid = infoUrl,
InfoUrl = infoUrl,
DownloadUrl = GetDownloadUrl(id, !result.IsFreeLeech && !result.IsNeutralLeech && !result.IsPersonalFreeLeech),
DownloadUrl = GetDownloadUrl(id, result.CanUseToken && !isFreeLeech),
Title = groupName,
Size = long.Parse(result.Size),
Seeders = int.Parse(result.Seeders),
@@ -133,7 +137,7 @@ public class GazelleParser : IParseIndexerResponse
Grabs = result.Snatches,
PublishDate = long.TryParse(result.GroupTime, out var num) ? DateTimeOffset.FromUnixTimeSeconds(num).UtcDateTime : DateTimeUtil.FromFuzzyTime((string)result.GroupTime),
PosterUrl = posterUrl,
DownloadVolumeFactor = result.IsFreeLeech || result.IsNeutralLeech || result.IsPersonalFreeLeech ? 0 : 1,
DownloadVolumeFactor = isFreeLeech ? 0 : 1,
UploadVolumeFactor = result.IsNeutralLeech ? 0 : 1
};
@@ -165,7 +169,7 @@ public class GazelleParser : IParseIndexerResponse
.AddQueryParam("action", "download")
.AddQueryParam("id", torrentId);
if (Settings.UseFreeleechToken)
if (Settings.UseFreeleechToken is (int)GazelleFreeleechTokenAction.Preferred or (int)GazelleFreeleechTokenAction.Required && canUseToken)
{
url = url.AddQueryParam("usetoken", "1");
}

View File

@@ -16,11 +16,23 @@ public class GazelleSettings : UserPassTorrentBaseSettings
public string AuthKey { get; set; }
public string PassKey { get; set; }
[FieldDefinition(5, Type = FieldType.Checkbox, Label = "Use Freeleech Token", HelpText = "Use freeleech tokens when available")]
public bool UseFreeleechToken { get; set; }
[FieldDefinition(5, Type = FieldType.Select, Label = "Use Freeleech Tokens", SelectOptions = typeof(GazelleFreeleechTokenAction), HelpText = "When to use freeleech tokens")]
public int UseFreeleechToken { get; set; }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
public enum GazelleFreeleechTokenAction
{
[FieldOption(Label = "Never", Hint = "Do not use tokens")]
Never = 0,
[FieldOption(Label = "Preferred", Hint = "Use token if possible")]
Preferred = 1,
[FieldOption(Label = "Required", Hint = "Abort download if unable to use token")]
Required = 2,
}

View File

@@ -168,8 +168,10 @@ public class GreatPosterWallParser : GazelleParser
{
foreach (var torrent in result.Torrents)
{
var isFreeLeech = torrent.IsFreeleech || torrent.IsNeutralLeech || torrent.IsPersonalFreeleech;
// skip releases that cannot be used with freeleech tokens when the option is enabled
if (_settings.UseFreeleechToken && !torrent.CanUseToken)
if (_settings.UseFreeleechToken == (int)GazelleFreeleechTokenAction.Required && !torrent.CanUseToken && !isFreeLeech)
{
continue;
}
@@ -181,7 +183,7 @@ public class GreatPosterWallParser : GazelleParser
{
Guid = infoUrl,
InfoUrl = infoUrl,
DownloadUrl = GetDownloadUrl(torrent.TorrentId, !torrent.IsFreeleech && !torrent.IsNeutralLeech && !torrent.IsPersonalFreeleech),
DownloadUrl = GetDownloadUrl(torrent.TorrentId, torrent.CanUseToken && !isFreeLeech),
Title = WebUtility.HtmlDecode(torrent.FileName).Trim(),
PosterUrl = GetPosterUrl(result.Cover),
PublishDate = new DateTimeOffset(time, TimeSpan.FromHours(8)).UtcDateTime, // Time is Chinese Time, add 8 hours difference from UTC
@@ -192,7 +194,7 @@ public class GreatPosterWallParser : GazelleParser
Grabs = torrent.Snatches,
Files = torrent.FileCount,
Scene = torrent.Scene,
DownloadVolumeFactor = torrent.IsFreeleech || torrent.IsNeutralLeech || torrent.IsPersonalFreeleech ? 0 : 1,
DownloadVolumeFactor = isFreeLeech ? 0 : 1,
UploadVolumeFactor = torrent.IsNeutralLeech ? 0 : 1,
MinimumRatio = 1,
MinimumSeedTime = 172800 // 48 hours
@@ -240,7 +242,7 @@ public class GreatPosterWallParser : GazelleParser
.AddQueryParam("action", "download")
.AddQueryParam("id", torrentId);
if (_settings.UseFreeleechToken && canUseToken)
if (_settings.UseFreeleechToken is (int)GazelleFreeleechTokenAction.Preferred or (int)GazelleFreeleechTokenAction.Required && canUseToken)
{
url = url.AddQueryParam("usetoken", "1");
}

View File

@@ -22,6 +22,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
@@ -51,16 +52,18 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IParseIndexerResponse GetParser()
{
return new MyAnonamouseParser(Settings, Capabilities.Categories, _httpClient, _cacheManager, _logger);
return new MyAnonamouseParser(Definition, Settings, Capabilities.Categories, _httpClient, _cacheManager, _logger);
}
public override async Task<IndexerDownloadResponse> Download(Uri link)
{
if (Settings.Freeleech)
{
_logger.Debug($"Attempting to use freeleech token for {link.AbsoluteUri}");
var downloadLink = link.RemoveQueryParam("canUseToken");
var idMatch = TorrentIdRegex.Match(link.AbsoluteUri);
if (Settings.Freeleech && bool.TryParse(link.GetQueryParam("canUseToken"), out var canUseToken) && canUseToken)
{
_logger.Debug("Attempting to use freeleech token for {0}", downloadLink.AbsoluteUri);
var idMatch = TorrentIdRegex.Match(downloadLink.AbsoluteUri);
if (idMatch.Success)
{
var id = int.Parse(idMatch.Groups["id"].Value);
@@ -79,20 +82,20 @@ namespace NzbDrone.Core.Indexers.Definitions
if (resource.Success)
{
_logger.Debug($"Successfully to used freeleech token for torrentid ${id}");
_logger.Debug("Successfully to used freeleech token for torrentid {0}", id);
}
else
{
_logger.Debug($"Failed to use freeleech token: ${resource.Error}");
_logger.Debug("Failed to use freeleech token: {0}", resource.Error);
}
}
else
{
_logger.Debug($"Could not get torrent id from link ${link.AbsoluteUri}, skipping freeleech");
_logger.Debug("Could not get torrent id from link {0}, skipping freeleech", downloadLink.AbsoluteUri);
}
}
return await base.Download(link).ConfigureAwait(false);
return await base.Download(downloadLink).ConfigureAwait(false);
}
protected override IDictionary<string, string> GetCookies()
@@ -372,6 +375,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class MyAnonamouseParser : IParseIndexerResponse
{
private readonly ProviderDefinition _definition;
private readonly MyAnonamouseSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private readonly IIndexerHttpClient _httpClient;
@@ -384,12 +388,14 @@ namespace NzbDrone.Core.Indexers.Definitions
"Elite VIP"
};
public MyAnonamouseParser(MyAnonamouseSettings settings,
public MyAnonamouseParser(ProviderDefinition definition,
MyAnonamouseSettings settings,
IndexerCapabilitiesCategories categories,
IIndexerHttpClient httpClient,
ICacheManager cacheManager,
Logger logger)
{
_definition = definition;
_settings = settings;
_categories = categories;
_httpClient = httpClient;
@@ -485,8 +491,10 @@ namespace NzbDrone.Core.Indexers.Definitions
release.Title += " [VIP]";
}
release.DownloadUrl = _settings.BaseUrl + "tor/download.php?tid=" + id;
release.InfoUrl = _settings.BaseUrl + "t/" + id;
var isFreeLeech = item.Free || item.PersonalFreeLeech || (hasUserVip && item.FreeVip);
release.DownloadUrl = GetDownloadUrl(id, !isFreeLeech);
release.InfoUrl = $"{_settings.BaseUrl}t/{id}";
release.Guid = release.InfoUrl;
release.Categories = _categories.MapTrackerCatToNewznab(item.Category);
release.PublishDate = DateTime.ParseExact(item.Added, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToLocalTime();
@@ -495,7 +503,7 @@ namespace NzbDrone.Core.Indexers.Definitions
release.Seeders = item.Seeders;
release.Peers = item.Leechers + release.Seeders;
release.Size = ParseUtil.GetBytes(item.Size);
release.DownloadVolumeFactor = item.Free ? 0 : hasUserVip && item.FreeVip ? 0 : 1;
release.DownloadVolumeFactor = isFreeLeech ? 0 : 1;
release.UploadVolumeFactor = 1;
release.MinimumRatio = 1;
release.MinimumSeedTime = 259200; // 72 hours
@@ -509,6 +517,20 @@ namespace NzbDrone.Core.Indexers.Definitions
return releaseInfos.ToArray();
}
private string GetDownloadUrl(int torrentId, bool canUseToken)
{
var url = new HttpUri(_settings.BaseUrl)
.CombinePath("/tor/download.php")
.AddQueryParam("tid", torrentId);
if (_settings.Freeleech && canUseToken)
{
url = url.AddQueryParam("canUseToken", "true");
}
return url.FullUri;
}
private bool HasUserVip(Dictionary<string, string> cookies)
{
var cacheKey = "myanonamouse_user_class_" + _settings.ToJson().SHA256Hash();
@@ -525,7 +547,7 @@ namespace NzbDrone.Core.Indexers.Definitions
_logger.Debug("Fetching user data: {0}", request.Url.FullUri);
var response = _httpClient.Get(request);
var response = _httpClient.ExecuteProxied(request, _definition);
var jsonResponse = JsonConvert.DeserializeObject<MyAnonamouseUserDataResponse>(response.Content);
return jsonResponse.UserClass?.Trim();
@@ -587,14 +609,19 @@ namespace NzbDrone.Core.Indexers.Definitions
{
[FieldOption(Label="All torrents", Hint = "Search everything")]
All = 0,
[FieldOption(Label="Only active", Hint = "Last update had 1+ seeders")]
Active = 1,
[FieldOption(Label="Freeleech", Hint = "Freeleech torrents")]
Freeleech = 2,
[FieldOption(Label="Freeleech or VIP", Hint = "Freeleech or VIP torrents")]
FreeleechOrVip = 3,
[FieldOption(Label="VIP", Hint = "VIP torrents")]
Vip = 4,
[FieldOption(Label="Not VIP", Hint = "Torrents not VIP")]
NotVip = 5,
}
@@ -611,6 +638,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public string Filetype { get; set; }
public bool Vip { get; set; }
public bool Free { get; set; }
[JsonProperty(PropertyName = "personal_freeleech")]
public bool PersonalFreeLeech { get; set; }
[JsonProperty(PropertyName = "fl_vip")]
public bool FreeVip { get; set; }
public string Category { get; set; }

View File

@@ -244,7 +244,9 @@ namespace NzbDrone.Core.Indexers.Definitions
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, "Unexpected response status '{0}' code from indexer request", indexerResponse.HttpResponse.StatusCode);
STJson.TryDeserialize<JsonRpcResponse<NebulanceErrorResponse>>(indexerResponse.HttpResponse.Content, out var errorResponse);
throw new IndexerException(indexerResponse, "Unexpected response status '{0}' code from indexer request: {1}", indexerResponse.HttpResponse.StatusCode, errorResponse?.Result?.Error?.Message ?? "Check the logs for more information.");
}
JsonRpcResponse<NebulanceResponse> jsonResponse;
@@ -410,4 +412,14 @@ namespace NzbDrone.Core.Indexers.Definitions
public IEnumerable<string> Tags { get; set; } = Array.Empty<string>();
}
public class NebulanceErrorResponse
{
public NebulanceErrorMessage Error { get; set; }
}
public class NebulanceErrorMessage
{
public string Message { get; set; }
}
}

View File

@@ -50,6 +50,20 @@ namespace NzbDrone.Core.Indexers.Definitions
return new OrpheusParser(Settings, Capabilities.Categories);
}
protected override Task<HttpRequest> GetDownloadRequest(Uri link)
{
var requestBuilder = new HttpRequestBuilder(link.AbsoluteUri)
{
AllowAutoRedirect = FollowRedirect
};
var request = requestBuilder
.SetHeader("Authorization", $"token {Settings.Apikey}")
.Build();
return Task.FromResult(request);
}
protected override IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> releases, SearchCriteriaBase searchCriteria)
{
var cleanReleases = base.CleanupReleases(releases, searchCriteria);
@@ -91,47 +105,28 @@ namespace NzbDrone.Core.Indexers.Definitions
public override async Task<IndexerDownloadResponse> Download(Uri link)
{
var request = new HttpRequestBuilder(link.AbsoluteUri)
.SetHeader("Authorization", $"token {Settings.Apikey}")
.Build();
var downloadResponse = await base.Download(link);
byte[] downloadBytes;
long elapsedTime;
var fileData = downloadResponse.Data;
try
if (Settings.UseFreeleechToken == (int)OrpheusFreeleechTokenAction.Preferred
&& fileData.Length >= 1
&& fileData[0] != 'd' // simple test for torrent vs HTML content
&& link.Query.Contains("usetoken=1"))
{
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
downloadBytes = response.ResponseData;
elapsedTime = response.ElapsedTime;
var html = Encoding.GetString(fileData);
if (downloadBytes.Length >= 1
&& downloadBytes[0] != 'd' // simple test for torrent vs HTML content
&& link.Query.Contains("usetoken=1"))
if (html.Contains("You do not have any freeleech tokens left.")
|| html.Contains("You do not have enough freeleech tokens")
|| html.Contains("This torrent is too large.")
|| html.Contains("You cannot use tokens here"))
{
var html = Encoding.GetString(downloadBytes);
if (html.Contains("You do not have any freeleech tokens left.")
|| html.Contains("You do not have enough freeleech tokens")
|| html.Contains("This torrent is too large.")
|| html.Contains("You cannot use tokens here"))
{
// download again without usetoken
request.Url = new HttpUri(link.ToString().Replace("&usetoken=1", ""));
response = await _httpClient.ExecuteProxiedAsync(request, Definition);
downloadBytes = response.ResponseData;
}
// Try to download again without usetoken
downloadResponse = await base.Download(link.RemoveQueryParam("usetoken"));
}
}
catch (Exception)
{
_indexerStatusService.RecordFailure(Definition.Id);
_logger.Error("Download failed");
throw;
}
ValidateDownloadData(downloadBytes);
return new IndexerDownloadResponse(downloadBytes, elapsedTime);
return downloadResponse;
}
}
@@ -280,7 +275,7 @@ namespace NzbDrone.Core.Indexers.Definitions
foreach (var torrent in result.Torrents)
{
// skip releases that cannot be used with freeleech tokens when the option is enabled
if (_settings.UseFreeleechToken && !torrent.CanUseToken)
if (_settings.UseFreeleechToken == (int)OrpheusFreeleechTokenAction.Required && !torrent.CanUseToken)
{
continue;
}
@@ -289,12 +284,13 @@ namespace NzbDrone.Core.Indexers.Definitions
var title = GetTitle(result, torrent);
var infoUrl = GetInfoUrl(result.GroupId, id);
var isFreeLeech = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsPersonalFreeLeech;
var release = new TorrentInfo
{
Guid = infoUrl,
InfoUrl = infoUrl,
DownloadUrl = GetDownloadUrl(id, !torrent.IsFreeLeech && !torrent.IsNeutralLeech && !torrent.IsPersonalFreeLeech),
DownloadUrl = GetDownloadUrl(id, torrent.CanUseToken && !isFreeLeech),
Title = WebUtility.HtmlDecode(title),
Artist = WebUtility.HtmlDecode(result.Artist),
Album = WebUtility.HtmlDecode(result.GroupName),
@@ -308,7 +304,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Scene = torrent.Scene,
Files = torrent.FileCount,
Grabs = torrent.Snatches,
DownloadVolumeFactor = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsPersonalFreeLeech ? 0 : 1,
DownloadVolumeFactor = isFreeLeech ? 0 : 1,
UploadVolumeFactor = torrent.IsNeutralLeech ? 0 : 1
};
@@ -330,19 +326,20 @@ namespace NzbDrone.Core.Indexers.Definitions
else
{
// skip releases that cannot be used with freeleech tokens when the option is enabled
if (_settings.UseFreeleechToken && !result.CanUseToken)
if (_settings.UseFreeleechToken == (int)OrpheusFreeleechTokenAction.Required && !result.CanUseToken)
{
continue;
}
var id = result.TorrentId;
var infoUrl = GetInfoUrl(result.GroupId, id);
var isFreeLeech = result.IsFreeLeech || result.IsNeutralLeech || result.IsPersonalFreeLeech;
var release = new TorrentInfo
{
Guid = infoUrl,
InfoUrl = infoUrl,
DownloadUrl = GetDownloadUrl(id, !result.IsFreeLeech && !result.IsNeutralLeech && !result.IsPersonalFreeLeech),
DownloadUrl = GetDownloadUrl(id, result.CanUseToken && !isFreeLeech),
Title = WebUtility.HtmlDecode(result.GroupName),
Size = long.Parse(result.Size),
Seeders = int.Parse(result.Seeders),
@@ -350,7 +347,7 @@ namespace NzbDrone.Core.Indexers.Definitions
PublishDate = long.TryParse(result.GroupTime, out var num) ? DateTimeOffset.FromUnixTimeSeconds(num).UtcDateTime : DateTimeUtil.FromFuzzyTime(result.GroupTime),
Files = result.FileCount,
Grabs = result.Snatches,
DownloadVolumeFactor = result.IsFreeLeech || result.IsNeutralLeech || result.IsPersonalFreeLeech ? 0 : 1,
DownloadVolumeFactor = isFreeLeech ? 0 : 1,
UploadVolumeFactor = result.IsNeutralLeech ? 0 : 1
};
@@ -416,7 +413,7 @@ namespace NzbDrone.Core.Indexers.Definitions
.AddQueryParam("id", torrentId);
// Orpheus fails to download if usetoken=0 so we need to only add if we will use one
if (_settings.UseFreeleechToken && canUseToken)
if (_settings.UseFreeleechToken is (int)OrpheusFreeleechTokenAction.Preferred or (int)OrpheusFreeleechTokenAction.Required && canUseToken)
{
url = url.AddQueryParam("usetoken", "1");
}
@@ -450,18 +447,30 @@ namespace NzbDrone.Core.Indexers.Definitions
public OrpheusSettings()
{
Apikey = "";
UseFreeleechToken = false;
UseFreeleechToken = (int)OrpheusFreeleechTokenAction.Never;
}
[FieldDefinition(2, Label = "ApiKey", HelpText = "IndexerOrpheusSettingsApiKeyHelpText", Privacy = PrivacyLevel.ApiKey)]
public string Apikey { get; set; }
[FieldDefinition(3, Label = "Use Freeleech Tokens", HelpText = "Use freeleech tokens when available", Type = FieldType.Checkbox)]
public bool UseFreeleechToken { get; set; }
[FieldDefinition(3, Type = FieldType.Select, Label = "Use Freeleech Tokens", SelectOptions = typeof(OrpheusFreeleechTokenAction), HelpText = "When to use freeleech tokens")]
public int UseFreeleechToken { get; set; }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
public enum OrpheusFreeleechTokenAction
{
[FieldOption(Label = "Never", Hint = "Do not use tokens")]
Never = 0,
[FieldOption(Label = "Preferred", Hint = "Use token if possible")]
Preferred = 1,
[FieldOption(Label = "Required", Hint = "Abort download if unable to use token")]
Required = 2,
}
}

View File

@@ -73,6 +73,6 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
public class PassThePopcornFlag : IndexerFlag
{
public static IndexerFlag Golden => new ("golden", "Release follows Golden Popcorn quality rules");
public static IndexerFlag Approved => new ("approved", "Release approved by PTP");
public static IndexerFlag Approved => new ("approved", "Release approved by PTP staff");
}
}

View File

@@ -86,6 +86,11 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
parameters.Set("freetorrent", "1");
}
if (_settings.GoldenPopcornOnly)
{
parameters.Set("scene", "2");
}
var queryCats = _capabilities.Categories
.MapTorznabCapsToTrackers(searchCriteria.Categories)
.Select(int.Parse)

View File

@@ -27,6 +27,9 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
[FieldDefinition(4, Label = "IndexerSettingsFreeleechOnly", HelpText = "IndexerPassThePopcornSettingsFreeleechOnlyHelpText", Type = FieldType.Checkbox)]
public bool FreeleechOnly { get; set; }
[FieldDefinition(5, Label = "IndexerPassThePopcornSettingsGoldenPopcornOnly", HelpText = "IndexerPassThePopcornSettingsGoldenPopcornOnlyHelpText", Type = FieldType.Checkbox, Advanced = true)]
public bool GoldenPopcornOnly { get; set; }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -102,6 +102,32 @@ namespace NzbDrone.Core.Indexers.Definitions
return caps;
}
public override async Task<IndexerDownloadResponse> Download(Uri link)
{
var downloadResponse = await base.Download(link);
var fileData = downloadResponse.Data;
if (Settings.UseFreeleechToken == (int)RedactedFreeleechTokenAction.Preferred
&& fileData.Length >= 1
&& fileData[0] != 'd' // simple test for torrent vs HTML content
&& link.Query.Contains("usetoken=1"))
{
var html = Encoding.GetString(fileData);
if (html.Contains("You do not have any freeleech tokens left.")
|| html.Contains("You do not have enough freeleech tokens")
|| html.Contains("This torrent is too large.")
|| html.Contains("You cannot use tokens here"))
{
// Try to download again without usetoken
downloadResponse = await base.Download(link.RemoveQueryParam("usetoken"));
}
}
return downloadResponse;
}
}
public class RedactedRequestGenerator : IIndexerRequestGenerator
@@ -248,7 +274,7 @@ namespace NzbDrone.Core.Indexers.Definitions
foreach (var torrent in result.Torrents)
{
// skip releases that cannot be used with freeleech tokens when the option is enabled
if (_settings.UseFreeleechToken && !torrent.CanUseToken)
if (_settings.UseFreeleechToken == (int)RedactedFreeleechTokenAction.Required && !torrent.CanUseToken)
{
continue;
}
@@ -263,12 +289,13 @@ namespace NzbDrone.Core.Indexers.Definitions
var title = GetTitle(result, torrent);
var infoUrl = GetInfoUrl(result.GroupId, id);
var isFreeLeech = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsFreeload || torrent.IsPersonalFreeLeech;
var release = new TorrentInfo
{
Guid = infoUrl,
InfoUrl = infoUrl,
DownloadUrl = GetDownloadUrl(id, !torrent.IsFreeLeech && !torrent.IsNeutralLeech && !torrent.IsFreeload && !torrent.IsPersonalFreeLeech),
DownloadUrl = GetDownloadUrl(id, torrent.CanUseToken && !isFreeLeech),
Title = WebUtility.HtmlDecode(title),
Artist = WebUtility.HtmlDecode(result.Artist),
Album = WebUtility.HtmlDecode(result.GroupName),
@@ -282,7 +309,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Scene = torrent.Scene,
Files = torrent.FileCount,
Grabs = torrent.Snatches,
DownloadVolumeFactor = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsFreeload || torrent.IsPersonalFreeLeech ? 0 : 1,
DownloadVolumeFactor = isFreeLeech ? 0 : 1,
UploadVolumeFactor = torrent.IsNeutralLeech || torrent.IsFreeload ? 0 : 1
};
@@ -304,7 +331,7 @@ namespace NzbDrone.Core.Indexers.Definitions
else
{
// skip releases that cannot be used with freeleech tokens when the option is enabled
if (_settings.UseFreeleechToken && !result.CanUseToken)
if (_settings.UseFreeleechToken == (int)RedactedFreeleechTokenAction.Required && !result.CanUseToken)
{
continue;
}
@@ -317,12 +344,13 @@ namespace NzbDrone.Core.Indexers.Definitions
var id = result.TorrentId;
var infoUrl = GetInfoUrl(result.GroupId, id);
var isFreeLeech = result.IsFreeLeech || result.IsNeutralLeech || result.IsFreeload || result.IsPersonalFreeLeech;
var release = new TorrentInfo
{
Guid = infoUrl,
InfoUrl = infoUrl,
DownloadUrl = GetDownloadUrl(id, !result.IsFreeLeech && !result.IsNeutralLeech && !result.IsFreeload && !result.IsPersonalFreeLeech),
DownloadUrl = GetDownloadUrl(id, result.CanUseToken && !isFreeLeech),
Title = WebUtility.HtmlDecode(result.GroupName),
Size = long.Parse(result.Size),
Seeders = int.Parse(result.Seeders),
@@ -330,7 +358,7 @@ namespace NzbDrone.Core.Indexers.Definitions
PublishDate = DateTimeOffset.FromUnixTimeSeconds(ParseUtil.CoerceLong(result.GroupTime)).UtcDateTime,
Files = result.FileCount,
Grabs = result.Snatches,
DownloadVolumeFactor = result.IsFreeLeech || result.IsNeutralLeech || result.IsFreeload || result.IsPersonalFreeLeech ? 0 : 1,
DownloadVolumeFactor = isFreeLeech ? 0 : 1,
UploadVolumeFactor = result.IsNeutralLeech || result.IsFreeload ? 0 : 1
};
@@ -395,7 +423,7 @@ namespace NzbDrone.Core.Indexers.Definitions
.AddQueryParam("action", "download")
.AddQueryParam("id", torrentId);
if (_settings.UseFreeleechToken && canUseToken)
if (_settings.UseFreeleechToken is (int)RedactedFreeleechTokenAction.Preferred or (int)RedactedFreeleechTokenAction.Required && canUseToken)
{
url = url.AddQueryParam("usetoken", "1");
}
@@ -429,14 +457,14 @@ namespace NzbDrone.Core.Indexers.Definitions
public RedactedSettings()
{
Apikey = "";
UseFreeleechToken = false;
UseFreeleechToken = (int)RedactedFreeleechTokenAction.Never;
}
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "IndexerRedactedSettingsApiKeyHelpText")]
public string Apikey { get; set; }
[FieldDefinition(3, Label = "Use Freeleech Tokens", Type = FieldType.Checkbox, HelpText = "Use freeleech tokens when available")]
public bool UseFreeleechToken { get; set; }
[FieldDefinition(3, Type = FieldType.Select, Label = "Use Freeleech Tokens", SelectOptions = typeof(RedactedFreeleechTokenAction), HelpText = "When to use freeleech tokens")]
public int UseFreeleechToken { get; set; }
[FieldDefinition(4, Label = "Freeload Only", Type = FieldType.Checkbox, Advanced = true, HelpTextWarning = "Search freeload torrents only. End date: 31 January 2024, 23:59 UTC.")]
public bool FreeloadOnly { get; set; }
@@ -446,4 +474,16 @@ namespace NzbDrone.Core.Indexers.Definitions
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
public enum RedactedFreeleechTokenAction
{
[FieldOption(Label = "Never", Hint = "Do not use tokens")]
Never = 0,
[FieldOption(Label = "Preferred", Hint = "Use token if possible")]
Preferred = 1,
[FieldOption(Label = "Required", Hint = "Abort download if unable to use token")]
Required = 2,
}
}

View File

@@ -226,7 +226,7 @@ public class SecretCinemaParser : IParseIndexerResponse
.AddQueryParam("action", "download")
.AddQueryParam("id", torrentId);
if (_settings.UseFreeleechToken)
if (_settings.UseFreeleechToken is (int)GazelleFreeleechTokenAction.Preferred or (int)GazelleFreeleechTokenAction.Required)
{
url = url.AddQueryParam("useToken", "1");
}

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 = "IndexerSettingsPreferMagnetUrl", HelpText = "IndexerSettingsPreferMagnetUrlHelpText", Advanced = true)]
public bool PreferMagnetUrl { get; set; }
}
}

View File

@@ -398,5 +398,8 @@
"Script": "Manuskript",
"BuiltIn": "Indbygget",
"PublishedDate": "Udgivelsesdato",
"AllSearchResultsHiddenByFilter": "Alle resultater skjules af det anvendte filter"
"AllSearchResultsHiddenByFilter": "Alle resultater skjules af det anvendte filter",
"AddApplication": "Tilføj Applikation",
"AddCategory": "Tilføj Kategori",
"ActiveApps": "Aktive Apps"
}

View File

@@ -320,6 +320,11 @@
"IndexerAlphaRatioSettingsFreeleechOnlyHelpText": "Search freeleech releases only",
"IndexerAlreadySetup": "At least one instance of indexer is already setup",
"IndexerAuth": "Indexer Auth",
"IndexerAvistazSettingsFreeleechOnlyHelpText": "Search freeleech releases only",
"IndexerAvistazSettingsPasswordHelpText": "Site Password",
"IndexerAvistazSettingsPidHelpText": "PID from My Account or My Profile page",
"IndexerAvistazSettingsUsernameHelpText": "Site Username",
"IndexerAvistazSettingsUsernameHelpTextWarning": "Only member rank and above can use the API on this indexer.",
"IndexerBeyondHDSettingsApiKeyHelpText": "API Key from the Site (Found in My Security => API Key)",
"IndexerBeyondHDSettingsFreeleechOnlyHelpText": "Search freeleech releases only",
"IndexerBeyondHDSettingsLimitedOnly": "Limited Only",
@@ -380,6 +385,8 @@
"IndexerPassThePopcornSettingsApiKeyHelpText": "Site API Key",
"IndexerPassThePopcornSettingsApiUserHelpText": "These settings are found in your PassThePopcorn security settings (Edit Profile > Security).",
"IndexerPassThePopcornSettingsFreeleechOnlyHelpText": "Search freeleech releases only",
"IndexerPassThePopcornSettingsGoldenPopcornOnly": "Golden Popcorn only",
"IndexerPassThePopcornSettingsGoldenPopcornOnlyHelpText": "Search Golden Popcorn releases only",
"IndexerPriority": "Indexer Priority",
"IndexerPriorityHelpText": "Indexer Priority from 1 (Highest) to 50 (Lowest). Default: 25.",
"IndexerProxies": "Indexer Proxies",
@@ -407,6 +414,8 @@
"IndexerSettingsPackSeedTime": "Pack Seed Time",
"IndexerSettingsPackSeedTimeIndexerHelpText": "The time a pack (season or discography) torrent should be seeded before stopping, empty is app's default",
"IndexerSettingsPasskey": "Pass Key",
"IndexerSettingsPreferMagnetUrl": "Prefer Magnet URL",
"IndexerSettingsPreferMagnetUrlHelpText": "When enabled, this indexer will prefer the use of magnet URLs for grabs with fallback to torrent links",
"IndexerSettingsQueryLimit": "Query Limit",
"IndexerSettingsQueryLimitHelpText": "The number of max queries as specified by the respective unit that {appName} will allow to the site",
"IndexerSettingsRssKey": "RSS Key",
@@ -541,6 +550,8 @@
"PendingChangesStayReview": "Stay and review changes",
"Port": "Port",
"PortNumber": "Port Number",
"PreferMagnetUrl": "Prefer Magnet URL",
"PreferMagnetUrlHelpText": "When enabled, this indexer will prefer the use of magnet URLs for grabs with fallback to torrent links",
"Presets": "Presets",
"Priority": "Priority",
"PrioritySettings": "Priority: {priority}",

View File

@@ -167,7 +167,7 @@
"URLBase": "URL base",
"Uptime": "Tiempo de actividad",
"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 integrado de {appName} o un script",
"UpdateMechanismHelpText": "Usa el actualizador integrado de {appName} o un script",
"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",
@@ -771,5 +771,18 @@
"AverageGrabs": "Promedio de capturas",
"AllSearchResultsHiddenByFilter": "Todos los resultados están ocultos por el filtro aplicado.",
"PackageVersionInfo": "{packageVersion} por {packageAuthor}",
"HealthMessagesInfoBox": "Puede encontrar más información sobre la causa de estos mensajes de comprobación de salud haciendo clic en el enlace wiki (icono de libro) al final de la fila, o comprobando sus [logs]({link}). Si tienes dificultades para interpretar estos mensajes, puedes ponerte en contacto con nuestro servicio de asistencia en los enlaces que aparecen a continuación."
"HealthMessagesInfoBox": "Puede encontrar más información sobre la causa de estos mensajes de comprobación de salud haciendo clic en el enlace wiki (icono de libro) al final de la fila, o comprobando sus [logs]({link}). Si tienes dificultades para interpretar estos mensajes, puedes ponerte en contacto con nuestro servicio de asistencia en los enlaces que aparecen a continuación.",
"LogSizeLimit": "Límite de tamaño de registro",
"LogSizeLimitHelpText": "Máximo tamaño de archivo de registro en MB antes de archivarlo. Predeterminado es 1MB.",
"PreferMagnetUrl": "Preferir URL magnet",
"IndexerSettingsPreferMagnetUrl": "Preferir URL magnet",
"IndexerSettingsPreferMagnetUrlHelpText": "Cuando está habilitado, este indexador preferirá el uso de URL magnet para capturas con alternativas a enlaces torrent",
"PreferMagnetUrlHelpText": "Cuando está habilitado, este indexador preferirá el uso de URL magnet para capturas con alternativas a enlaces torrent",
"IndexerPassThePopcornSettingsGoldenPopcornOnly": "Solo Golden Popcorn",
"IndexerPassThePopcornSettingsGoldenPopcornOnlyHelpText": "Busca lanzamientos solo en Golden Popcorn",
"IndexerAvistazSettingsFreeleechOnlyHelpText": "Buscar solo lanzamientos freeleech",
"IndexerAvistazSettingsUsernameHelpText": "Nombre de usuario del sitio",
"IndexerAvistazSettingsUsernameHelpTextWarning": "Solo los miembros de rango y superiores pueden usar la API en este indexador.",
"IndexerAvistazSettingsPasswordHelpText": "Contraseña del sitio",
"IndexerAvistazSettingsPidHelpText": "PID de la página de Mi cuenta o Mi perfil"
}

View File

@@ -57,7 +57,7 @@
"Proxy": "Proxy",
"Protocol": "Protocole",
"Options": "Options",
"NoChanges": "Aucuns changements",
"NoChanges": "Aucun changement",
"NoChange": "Pas de changement",
"MoreInfo": "Plus d'informations",
"Grabbed": "Saisie",
@@ -509,7 +509,7 @@
"DeleteSelectedDownloadClients": "Supprimer le(s) client(s) de téléchargement",
"DeleteSelectedDownloadClientsMessageText": "Voulez-vous vraiment supprimer {count} client(s) de téléchargement sélectionné(s) ?",
"StopSelecting": "Effacer la sélection",
"UpdateAvailableHealthCheckMessage": "Une nouvelle mise à jour est disponible",
"UpdateAvailableHealthCheckMessage": "Une nouvelle mise à jour est disponible : {version}",
"AdvancedSettingsHiddenClickToShow": "Paramètres avancés masqués, cliquez pour afficher",
"AdvancedSettingsShownClickToHide": "Paramètres avancés affichés, cliquez pour masquer",
"AppsMinimumSeeders": "Apps avec le nombre minimum de seeders disponibles",
@@ -644,7 +644,7 @@
"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",
"IndexerSettingsSeedTime": "Temps d'envoi",
"IndexerTorrentSyndikatSettingsApiKeyHelpText": "Clé API du site",
"BlackholeFolderHelpText": "Dossier dans lequel {appName} stockera le fichier {extension}",
"DefaultCategory": "Catégorie par défaut",
@@ -692,7 +692,7 @@
"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",
"IndexerSettingsSeedRatio": "Ratio d'envoi",
"IndexerSettingsVipExpiration": "Expiration de la carte VIP",
"SecretToken": "Jeton secret",
"TorrentBlackholeSaveMagnetFiles": "Enregistrer les fichiers magnétiques",
@@ -771,5 +771,9 @@
"AverageGrabs": "Prises moyennes",
"AverageQueries": "Requêtes moyennes",
"PackageVersionInfo": "{packageVersion} par {packageAuthor}",
"HealthMessagesInfoBox": "Vous pouvez trouver plus d'informations sur la cause de ces messages de contrôle de santé en cliquant sur le lien wiki (icône de livre) à la fin de la ligne, ou en vérifiant vos [journaux]({link}). Si vous rencontrez des difficultés pour interpréter ces messages, vous pouvez contacter notre support, via les liens ci-dessous."
"HealthMessagesInfoBox": "Vous pouvez trouver plus d'informations sur la cause de ces messages de contrôle de santé en cliquant sur le lien wiki (icône de livre) à la fin de la ligne, ou en vérifiant vos [journaux]({link}). Si vous rencontrez des difficultés pour interpréter ces messages, vous pouvez contacter notre support, via les liens ci-dessous.",
"LogSizeLimit": "Limite de taille du journal",
"LogSizeLimitHelpText": "Taille maximale du fichier journal en Mo avant archivage. La valeur par défaut est de 1 Mo.",
"IndexerAvistazSettingsFreeleechOnlyHelpText": "Rechercher les publications freeleech uniquement",
"IndexerAvistazSettingsUsernameHelpText": "Nom d'utilisateur du site"
}

View File

@@ -181,5 +181,32 @@
"Directory": "Direktorij",
"BuiltIn": "Ugrađeno",
"Redirected": "Preusmjeri",
"AllSearchResultsHiddenByFilter": "Svi rezultati su skriveni zbog primjenjenog filtera"
"AllSearchResultsHiddenByFilter": "Svi rezultati su skriveni zbog primjenjenog filtera",
"ApplyChanges": "Primjeni Promjene",
"ApiKeyValidationHealthCheckMessage": "Molimo ažuriraj svoj API ključ da ima barem {length} znakova. Ovo možeš uraditi u postavkama ili konfiguracijskoj datoteci",
"AppUpdated": "{appName} Ažuriran",
"AuthenticationRequired": "Potrebna Autentikacija",
"AddConnection": "Dodaj vezu",
"AddDownloadClientImplementation": "Dodaj Klijenta za Preuzimanje- {implementationName}",
"AddIndexerImplementation": "Dodaj Indexer - {implementationName}",
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Potvrdi novu lozinku",
"AuthenticationRequiredPasswordHelpTextWarning": "Unesi novu lozinku",
"AddConnectionImplementation": "Dodaj Vezu - {implementationName}",
"Any": "BIlo koji",
"AppUpdatedVersion": "{appName} je ažuriran na verziju '{version}', kako bi najnovije promjene bile aktivne potrebno je ponovno učitati {appName}",
"AuthenticationMethod": "Metoda Autentikacije",
"AuthenticationRequiredUsernameHelpTextWarning": "Unesi novo korisničko ime",
"AuthenticationMethodHelpTextWarning": "Molimo odaberi ispravnu metodu autentikacije",
"AuthenticationRequiredWarning": "Kako bi se spriječio udaljeni pristup bez autentikacije, {appName} sad zahtjeva da autentikacija bude omogućena. Izborno se može onemogućiti autentikacija s lokalnih adresa.",
"UnableToAddANewIndexerPleaseTryAgain": "Neuspješno dodavanje novog indexera, molimo pokušaj ponovno.",
"UnableToAddANewNotificationPleaseTryAgain": "Neuspješno dodavanje nove obavijesti, molimo pokušaj ponovno.",
"UnableToAddANewDownloadClientPleaseTryAgain": "Nesupješno dodavanje klijenta za preuzimanje, molimo pokušaj ponovno.",
"EditDownloadClientImplementation": "Dodaj Klijenta za Preuzimanje- {implementationName}",
"EditConnectionImplementation": "Dodaj Vezu - {implementationName}",
"UnableToAddANewIndexerProxyPleaseTryAgain": "Neuspješno dodavanje novog indexera, molimo pokušaj ponovno.",
"AddApplicationImplementation": "Dodaj Vezu - {implementationName}",
"UnableToAddANewAppProfilePleaseTryAgain": "Neuspješno dodavanje novog profila kvalitete, molimo pokušaj ponovno.",
"EditIndexerImplementation": "Dodaj Indexer - {implementationName}",
"AddIndexerProxyImplementation": "Dodaj Indexer - {implementationName}",
"UnableToAddANewApplicationPleaseTryAgain": "Neuspješno dodavanje nove obavijesti, molimo pokušaj ponovno."
}

View File

@@ -144,7 +144,7 @@
"ConnectionLost": "Connessione Persa",
"Component": "Componente",
"Columns": "Colonne",
"DeleteBackupMessageText": "Sei sicuro di voler cancellare il backup '{name}'?",
"DeleteBackupMessageText": "Sei sicuro di voler eliminare il backup '{name}'?",
"CancelPendingTask": "Sei sicuro di voler cancellare questa operazione in sospeso?",
"BranchUpdateMechanism": "Ramo utilizzato dal sistema di aggiornamento esterno",
"BranchUpdate": "Branca da usare per aggiornare {appName}",

View File

@@ -498,5 +498,7 @@
"Script": "Script",
"PublishedDate": "Publicatie Datum",
"Redirected": "Omleiden",
"AllSearchResultsHiddenByFilter": "Alle resultaten zijn verborgen door het toegepaste filter"
"AllSearchResultsHiddenByFilter": "Alle resultaten zijn verborgen door het toegepaste filter",
"Clone": "Kloon",
"DownloadClientSettingsUrlBaseHelpText": "Voegt een voorvoegsel toe aan de {connectionName} url, zoals {url}"
}

View File

@@ -109,7 +109,7 @@
"AuthenticationMethodHelpText": "Wymagaj nazwy użytkownika i hasła, aby uzyskać dostęp do {appName}",
"BackupFolderHelpText": "Względne ścieżki będą znajdować się w katalogu AppData {appName}",
"BackupRetentionHelpText": "Automatyczne kopie zapasowe starsze niż okres przechowywania zostaną automatycznie wyczyszczone",
"BindAddressHelpText": "Prawidłowy adres IP4 lub „*” dla wszystkich interfejsów",
"BindAddressHelpText": "Prawidłowy adres IP, localhost lub '*' dla wszystkich interfejsów",
"BranchUpdateMechanism": "Gałąź używana przez zewnętrzny mechanizm aktualizacji",
"BypassProxyForLocalAddresses": "Pomijaj serwer proxy dla adresów lokalnych",
"CancelPendingTask": "Czy na pewno chcesz anulować to oczekujące zadanie?",
@@ -124,9 +124,9 @@
"DatabaseMigration": "Migracja bazy danych",
"DeleteApplicationMessageText": "Czy na pewno chcesz usunąć powiadomienie „{0}”?",
"DeleteBackup": "Usuń kopię zapasową",
"DeleteBackupMessageText": "Czy na pewno chcesz usunąć kopię zapasową „{0}”?",
"DeleteBackupMessageText": "Czy na pewno chcesz usunąć kopię zapasową „{name}”?",
"DeleteDownloadClient": "Usuń klienta pobierania",
"DeleteDownloadClientMessageText": "Czy na pewno chcesz usunąć klienta pobierania „{0}”?",
"DeleteDownloadClientMessageText": "Czy na pewno chcesz usunąć klienta pobierania „{name}”?",
"DeleteNotification": "Usuń powiadomienie",
"Disabled": "Wyłączone",
"Docker": "Doker",
@@ -237,7 +237,7 @@
"SSLCertPathHelpText": "Ścieżka do pliku pfx",
"SSLPort": "Port SSL",
"StartTypingOrSelectAPathBelow": "Zacznij pisać lub wybierz ścieżkę poniżej",
"StartupDirectory": "Katalog startowy",
"StartupDirectory": "Katalog Startowy",
"Status": "Status",
"SuggestTranslationChange": "Zaproponuj zmianę tłumaczenia",
"SystemTimeCheckMessage": "Czas systemowy jest wyłączony o więcej niż 1 dzień. Zaplanowane zadania mogą nie działać poprawnie, dopóki czas nie zostanie skorygowany",
@@ -382,7 +382,7 @@
"EditDownloadClientImplementation": "Dodaj klienta pobierania - {implementationName}",
"Id": "Identyfikator",
"AddApplicationImplementation": "Dodaj Connection - {implementationName}",
"AddIndexerImplementation": "Dodaj condition - {implementationName}",
"AddIndexerImplementation": "Dodaj indeks - {implementationName}",
"AddIndexerProxyImplementation": "Dodaj condition - {implementationName}",
"EditConnectionImplementation": "Dodaj Connection - {implementationName}",
"EditApplicationImplementation": "Dodaj Connection - {implementationName}",
@@ -397,5 +397,23 @@
"Script": "Scenariusz",
"BuiltIn": "Wbudowany",
"PublishedDate": "Data publikacji",
"AllSearchResultsHiddenByFilter": "Wszystkie wyniki są ukrywane przez zastosowany filtr"
"AllSearchResultsHiddenByFilter": "Wszystkie wyniki są ukrywane przez zastosowany filtr",
"AppUpdated": "{appName} Zaktualizowany",
"AppUpdatedVersion": "{appName} został zaktualizowany do wersji `{version}`, by uzyskać nowe zmiany należy przeładować {appName}",
"AddCustomFilter": "Dodaj niestandardowy filtr",
"AuthenticationMethodHelpTextWarning": "Wybierz prawidłową metodę autoryzacji",
"Any": "Dowolny",
"AuthenticationMethod": "Metoda Autoryzacji",
"AuthenticationRequired": "Wymagana Autoryzacja",
"Categories": "Kategorie",
"Label": "Etykieta",
"Notification": "Powiadomienia",
"Season": "Sezon",
"Theme": "Motyw",
"Artist": "artysta",
"Album": "album",
"Connect": "Powiadomienia",
"Episode": "odcinek",
"Notifications": "Powiadomienia",
"Publisher": "Wydawca"
}

View File

@@ -771,5 +771,18 @@
"AverageGrabs": "Média de Capturas",
"AllSearchResultsHiddenByFilter": "Todos os resultados da pesquisa são ocultados pelo filtro aplicado.",
"PackageVersionInfo": "{packageVersion} por {packageAuthor}",
"HealthMessagesInfoBox": "Para saber mais sobre a causa dessas mensagens de verificação de integridade, clique no link da wiki (ícone de livro) no final da linha ou verifique os [logs]({link}). Se tiver dificuldade em interpretar essas mensagens, entre em contato com nosso suporte nos links abaixo."
"HealthMessagesInfoBox": "Para saber mais sobre a causa dessas mensagens de verificação de integridade, clique no link da wiki (ícone de livro) no final da linha ou verifique os [logs]({link}). Se tiver dificuldade em interpretar essas mensagens, entre em contato com nosso suporte nos links abaixo.",
"LogSizeLimit": "Limite de Tamanho do Registro",
"LogSizeLimitHelpText": "Tamanho máximo do arquivo de registro em MB antes do arquivamento. O padrão é 1 MB.",
"PreferMagnetUrlHelpText": "Quando ativado, este indexador preferirá o uso de URLs magnéticos para captura com substituto para links de torrent",
"IndexerSettingsPreferMagnetUrl": "Preferir URL Magnético",
"IndexerSettingsPreferMagnetUrlHelpText": "Quando ativado, este indexador preferirá o uso de URLs magnéticos para captura com substituto para links de torrent",
"PreferMagnetUrl": "Preferir URL Magnético",
"IndexerPassThePopcornSettingsGoldenPopcornOnly": "Apenas Golden Popcorn",
"IndexerPassThePopcornSettingsGoldenPopcornOnlyHelpText": "Pesquisar somente lançamentos em Golden Popcorn",
"IndexerAvistazSettingsFreeleechOnlyHelpText": "Pesquisar apenas lançamentos freeleech",
"IndexerAvistazSettingsUsernameHelpText": "Nome de Usuário do Site",
"IndexerAvistazSettingsPasswordHelpText": "Senha do Site",
"IndexerAvistazSettingsPidHelpText": "PID da página Minha Conta ou Meu Perfil",
"IndexerAvistazSettingsUsernameHelpTextWarning": "Somente membros com rank e acima podem usar a API neste indexador."
}

View File

@@ -8,16 +8,16 @@
"BackupIntervalHelpText": "Периодичность автоматического резервного копирования",
"Backup": "Резервное копирование",
"AutomaticSearch": "Автоматический поиск",
"Authentication": "Аутентификация",
"ApplyTags": "Применить тэги",
"Authentication": "Авторизация",
"ApplyTags": "Применить теги",
"Apply": "Применить",
"AppDataLocationHealthCheckMessage": "Обновление будет не возможно, во избежание удаления данных программы во время обновления",
"AppDataLocationHealthCheckMessage": "Обновление не позволит сохранить AppData при обновлении",
"ApiKey": "API ключ",
"Analytics": "Аналитика",
"AddIndexer": "Добавить индексер",
"AddIndexer": "Добавить индексатор",
"Added": "Добавлено",
"Actions": "Действия",
"About": "Об",
"About": "О программе",
"DeleteBackupMessageText": "Вы уверены, что хотите удалить резервную копию '{name}'?",
"DeleteBackup": "Удалить резервную копию",
"Delete": "Удалить",
@@ -27,41 +27,41 @@
"Rss": "RSS",
"SendAnonymousUsageData": "Отправка анонимных данных об использовании",
"UnableToLoadHistory": "Не удалось загрузить историю",
"MoreInfo": "Ещё инфо",
"MovieIndexScrollTop": "Индекс фильма: промотать вверх",
"MoreInfo": "Больше информации",
"MovieIndexScrollTop": "Индекс фильма: Прокрутить вверх",
"Size": "Размер",
"OpenBrowserOnStart": "Открывать браузер при запуске",
"OpenBrowserOnStart": "Открыть браузер при запуске",
"OpenThisModal": "Открыть это модальное окно",
"Options": "Опции",
"Options": "Параметры",
"PackageVersion": "Версия пакета",
"PageSize": "Размер страницы",
"PageSizeHelpText": "Количество показываемое на каждой страницы",
"PageSizeHelpText": "Количество элементов, отображаемых на каждой странице",
"Password": "Пароль",
"Peers": "Пиры",
"PendingChangesDiscardChanges": "Не применять изменения и выйти",
"PendingChangesStayReview": "Оставайтесь и просмотрите изменения",
"PendingChangesDiscardChanges": "Отменить изменения и выйти",
"PendingChangesStayReview": "Остаться и просмотреть изменения",
"Port": "Порт",
"PortNumber": "Номер порта",
"LogFiles": "Файлы журнала",
"Details": "Подробности",
"DownloadClients": "Клиенты для скачивания",
"DownloadClients": "Клиенты загрузки",
"Filename": "Имя файла",
"Files": "Файлы",
"Filter": "Фильтр",
"IndexerStatusAllUnavailableHealthCheckMessage": "Все индексаторы недоступны из-за ошибок",
"LogLevel": "Уровень журнала",
"LogLevelTraceHelpTextWarning": "Отслеживание журнала следует включать только временно",
"LogLevel": "Уровень журналирования",
"LogLevelTraceHelpTextWarning": "Включение трассировки журнала должно быть временным",
"Logs": "Журналы",
"Manual": "Ручной",
"Mechanism": "Механизм",
"NoBackupsAreAvailable": "Нет резервных копий",
"DeleteDownloadClientMessageText": "Вы уверены, что хотите удалить клиент загрузки '{name}'?",
"Edit": "Редактирование",
"Edit": "Редактировать",
"Enable": "Включить",
"Indexer": "Индексатор",
"SettingsShortDateFormat": "Короткий формат даты",
"SettingsShowRelativeDates": "Показать относительные даты",
"SettingsShowRelativeDatesHelpText": "Показывать относительные (сегодня / вчера / и т. д.) или абсолютные даты",
"SettingsShowRelativeDatesHelpText": "Показывать относительные (Сегодня/Вчера/и т.п.) или абсолютные даты",
"Docker": "Docker",
"FocusSearchBox": "Поле поиска в фокусе",
"Reset": "Сброс",
@@ -73,26 +73,26 @@
"Usenet": "Usenet",
"AcceptConfirmationModal": "Окно подтверждения",
"Today": "Сегодня",
"DeleteIndexerProxyMessageText": "Вы уверены, что хотите удалить тэг '{0}'?",
"ExistingTag": "Существующий тэг",
"DeleteIndexerProxyMessageText": "Вы уверены, что хотите удалить прокси индексатора '{name}'?",
"ExistingTag": "Существующий тег",
"Failed": "Неудачно",
"IndexerProxyStatusUnavailableHealthCheckMessage": "Индексаторы недоступны из-за ошибок: {indexerProxyNames}",
"IndexerProxyStatusUnavailableHealthCheckMessage": "Прокси индексатора недоступны из-за ошибок: {indexerProxyNames}",
"Indexers": "Индексаторы",
"InteractiveSearch": "Интерактивный поиск",
"Interval": "Интервал",
"KeyboardShortcuts": "Горячие клавиши",
"Language": "Язык",
"LastWriteTime": "Последнее время записи",
"LaunchBrowserHelpText": " Открывать браузер и переходить на страницу {appName} при запуске программы.",
"LaunchBrowserHelpText": " Открыть браузер и перейти на страницу {appName} при запуске приложения.",
"Level": "Уровень",
"Ok": "Хорошо",
"AddDownloadClient": "Добавить программу для скачивания",
"UpdateMechanismHelpText": "Используйте встроенную в {appName} функцию обновления или скрипт",
"Ok": "Ок",
"AddDownloadClient": "Добавить клиент загрузки",
"UpdateMechanismHelpText": "Использовать встроенный инструмент обновления {appName} или скрипт.",
"IndexerStatusUnavailableHealthCheckMessage": "Индексаторы недоступны из-за ошибок: {indexerNames}",
"NoTagsHaveBeenAddedYet": "Теги еще не добавлены",
"UnableToLoadTags": "Невозможно загрузить теги",
"AnalyticsEnabledHelpText": "Отправлять в {appName} анонимную информацию об использовании и ошибках. Анонимная статистика включает в себя информацию о браузере, какие страницы веб-интерфейса {appName} загружены, сообщения об ошибках, а также операционной системе. Мы используем эту информацию для выявления ошибок, а также для разработки нового функционала.",
"AuthenticationMethodHelpText": "Необходим логин и пароль для доступа в {appName}",
"NoTagsHaveBeenAddedYet": "Теги ещё не добавлены",
"UnableToLoadTags": "Не удалось загрузить теги",
"AnalyticsEnabledHelpText": "Отправлять анонимную информацию об использовании и ошибках на серверы {appName}. Анонимная статистика включает в себя информацию о вашем браузере, какие страницы веб-интерфейса {appName} вы используете, отчёты об ошибках, а также версию операционной системы и среды выполнения. Мы будем использовать эту информацию для разработки нового функционала и исправления ошибок.",
"AuthenticationMethodHelpText": "Необходимо ввести имя пользователя и пароль для доступа к {appName}",
"BackupFolderHelpText": "Относительные пути будут в каталоге AppData {appName}",
"BeforeUpdate": "До обновления",
"BindAddress": "Привязать адрес",
@@ -101,8 +101,8 @@
"BranchUpdate": "Ветвь для обновления {appName}",
"BranchUpdateMechanism": "Ветвь, используемая внешним механизмом обновления",
"BypassProxyForLocalAddresses": "Обход прокси для локальных адресов",
"Cancel": "Отменить",
"CancelPendingTask": "Вы уверены, что хотите убрать данную задачу из очереди?",
"Cancel": "Отмена",
"CancelPendingTask": "Вы уверены, что хотите отменить эту ожидающую задачу?",
"CertificateValidation": "Проверка сертификата",
"CertificateValidationHelpText": "Изменить строгое подтверждение сертификации НТТР",
"ChangeHasNotBeenSavedYet": "Изменения ещё не вступили в силу",
@@ -113,37 +113,37 @@
"ConnectionLost": "Соединение прервано",
"Connections": "Соединения",
"ConnectSettings": "Настройки соединения",
"CouldNotConnectSignalR": "Не возможно подключиться к SignalR, интерфейс не будет обновляться",
"CouldNotConnectSignalR": "Не удалось подключиться к SignalR, интерфейс не будет обновляться",
"Custom": "Настраиваемый",
"CustomFilters": "Настраиваемые фильтры",
"Date": "Дата",
"Dates": "Даты",
"DatabaseMigration": "Перенос БД",
"DatabaseMigration": "Миграция базы данных",
"DeleteNotification": "Удалить уведомление",
"DeleteNotificationMessageText": "Вы уверены, что хотите удалить уведомление '{name}'?",
"DeleteTag": "Удалить тэг",
"DeleteTagMessageText": "Вы уверены, что хотите удалить тэг '{label}'?",
"DeleteTag": "Удалить тег",
"DeleteTagMessageText": "Вы уверены, что хотите удалить тег '{label}'?",
"Disabled": "Выключено",
"Discord": "Discord",
"Donations": "Пожертвования",
"DownloadClient": "Загрузочный клиент",
"DownloadClientSettings": "Настройки клиента скачиваний",
"DownloadClientStatusAllClientHealthCheckMessage": "Все клиенты для скачивания недоступны из-за ошибок",
"DownloadClientStatusSingleClientHealthCheckMessage": "Клиенты для скачивания недоступны из-за ошибок: {downloadClientNames}",
"DownloadClient": "Клиент загрузки",
"DownloadClientSettings": "Настройки клиента загрузки",
"DownloadClientStatusAllClientHealthCheckMessage": "Все клиенты загрузок недоступны из-за ошибок",
"DownloadClientStatusSingleClientHealthCheckMessage": "Клиенты загрузок недоступны из-за ошибок: {downloadClientNames}",
"EnableAutomaticSearch": "Включить автоматический поиск",
"EnableAutomaticSearchHelpText": "Будет использовано для автоматических поисков через интерфейс или {appName}",
"EnableAutomaticSearchHelpText": "Будет использовано при автоматическом поиске через интерфейс или {appName}",
"Enabled": "Включено",
"EnableInteractiveSearch": "Включить интерактивный поиск",
"EnableRss": "Включить RSS",
"Fixed": "Исправлено",
"Folder": "Папка",
"ForMoreInformationOnTheIndividualDownloadClients": "Для дополнительной информации пл программам скачивания нажмите эту кнопку.",
"General": "Основное",
"GeneralSettings": "Основные настройки",
"GeneralSettingsSummary": "Порт, SSL, логин/пароль, прокси, аналитика и обновления",
"Folder": "Каталог",
"ForMoreInformationOnTheIndividualDownloadClients": "Для получения дополнительной информации о каждом клиенте загрузки нажмите на кнопки информации.",
"General": "Общие",
"GeneralSettings": "Общие настройки",
"GeneralSettingsSummary": "Порт, SSL, имя пользователя/пароль, прокси-сервер, аналитика и обновления",
"Grabbed": "Захвачено",
"Grabs": "Захватить",
"Health": "Здоровье",
"Grabs": "Захваты",
"Health": "Состояние",
"NoIssuesWithYourConfiguration": "С вашей конфигурацией нет проблем",
"HideAdvanced": "Скрыть расширенные",
"History": "История",
@@ -152,40 +152,40 @@
"Hostname": "Имя хоста",
"IgnoredAddresses": "Игнорируемые адреса",
"IllRestartLater": "Перезапущу позднее",
"IncludeHealthWarningsHelpText": "Включая предупреждения о здоровье",
"IndexerLongTermStatusAllUnavailableHealthCheckMessage": "Все индексаторы недоступны из-за ошибок за последние 6 часов",
"IndexerLongTermStatusUnavailableHealthCheckMessage": "Все индексаторы недоступны из-за ошибок за последние 6 часов: {indexerNames}",
"IncludeHealthWarningsHelpText": "Включая предупреждения о состоянии",
"IndexerLongTermStatusAllUnavailableHealthCheckMessage": "Все индексаторы недоступны из-за ошибок более 6 часов",
"IndexerLongTermStatusUnavailableHealthCheckMessage": "Все индексаторы недоступны из-за ошибок более 6 часов: {indexerNames}",
"IndexerPriority": "Приоритет индексатора",
"IndexerPriorityHelpText": "Приоритет индексаторов от 1 (наивысший) до 50 (низший). По-умолчанию: 25.",
"IndexerProxyStatusAllUnavailableHealthCheckMessage": "Все индексаторы недоступны из-за ошибок",
"IndexerPriorityHelpText": "Приоритет индексатора от 1 (Высший) до 50 (Низший). По умолчанию 25.",
"IndexerProxyStatusAllUnavailableHealthCheckMessage": "Все прокси индексатора недоступны из-за ошибок",
"Info": "Информация",
"Logging": "Ведение журнала",
"Logging": "Журналирование",
"Message": "Сообщение",
"MIA": "MIA",
"Mode": "Режим",
"MovieIndexScrollBottom": "Индекс фильма: промотать в низ",
"MovieIndexScrollBottom": "Индекс фильма: Прокрутить вниз",
"Name": "Имя",
"New": "Новый",
"NoChanges": "Нет изменений",
"NoLeaveIt": "Нет, оставить",
"NoLogFiles": "Нет файлов журнала",
"NoUpdatesAreAvailable": "Нет обновлений",
"OAuthPopupMessage": "Ваш браузер блокирует всплывающие окна",
"OnHealthIssueHelpText": о вопросам здоровья",
"NoLogFiles": "Файлов журнала нет",
"NoUpdatesAreAvailable": "Обновления отсутствуют",
"OAuthPopupMessage": "Всплывающие окна блокируются вашим браузером",
"OnHealthIssueHelpText": ри проблемах с состоянием",
"PendingChangesMessage": "У вас есть несохраненные изменения. Вы уверены, что хотите покинуть эту страницу?",
"Presets": "Пресеты",
"Protocol": "Протокол",
"Proxy": "Прокси",
"ProxyBypassFilterHelpText": "Используйте ',' в качестве разделителя и '*.' как подстановочный знак для субдоменов",
"ProxyBadRequestHealthCheckMessage": "Не удалось проверить прокси. Код: {statusCode}",
"ProxyFailedToTestHealthCheckMessage": "Не удалось проверить прокси: {url}",
"ProxyBypassFilterHelpText": "Используйте ',' как разделитель и '*.' как подстановочный знак для поддоменов",
"ProxyBadRequestHealthCheckMessage": "Не удалось протестировать прокси. Код состояния: {statusCode}",
"ProxyFailedToTestHealthCheckMessage": "Не удалось протестировать прокси: {url}",
"ProxyResolveIpHealthCheckMessage": "Не удалось определить IP-адрес настроенного прокси-хоста {proxyHostName}",
"ProxyPasswordHelpText": "Вам нужно только ввести имя пользователя и пароль, если они необходимы. В противном случае оставьте их пустыми.",
"ProxyPasswordHelpText": "Вы должны указать имя пользователя и пароль только если они необходимы. В противном случае оставьте эти поля пустыми.",
"ProxyType": "Тип прокси",
"ProxyUsernameHelpText": "Вам нужно только ввести имя пользователя и пароль, если они необходимы. В противном случае оставьте их пустыми.",
"ProxyUsernameHelpText": "Вы должны указать имя пользователя и пароль только если они необходимы. В противном случае оставьте эти поля пустыми.",
"Queue": "Очередь",
"ReadTheWikiForMoreInformation": "Прочтите Wiki для получения дополнительной информации",
"ReleaseBranchCheckOfficialBranchMessage": "Ветка {0} не является допустимой веткой выпуска {appName}, вы не будете получать обновления",
"ReleaseBranchCheckOfficialBranchMessage": "Ветвь {0} не является действительной релизной ветвью {appName}, вы не будете получать обновления",
"ReleaseStatus": "Статус релиза",
"Reload": "Перезагрузить",
"RemovedFromTaskQueue": "Удалено из очереди задач",
@@ -196,7 +196,7 @@
"RestartNow": "Перезапустить сейчас",
"RestartRequiredHelpTextWarning": "Для применения изменений, требуется перезапуск",
"Restore": "Восстановить",
"RestoreBackup": "Восстановить из резервной копии",
"RestoreBackup": "Восстановить резервную копию",
"Result": "Результат",
"Retention": "Удержание",
"RssIsNotSupportedWithThisIndexer": "RSS не поддерживается этим индексатором",
@@ -207,148 +207,148 @@
"ScriptPath": "Путь к скрипту",
"Search": "Поиск",
"Security": "Безопасность",
"Seeders": "Сиды",
"Seeders": "Сидеры",
"SelectAll": "Выбрать все",
"SetTags": "Установить теги",
"Settings": "Настройки",
"SettingsEnableColorImpairedMode": "Версия для слабовидящих",
"SettingsEnableColorImpairedModeHelpText": "Стиль изменён чтобы слабовидящие лучше различали цвета",
"SettingsLongDateFormat": "Длинный формат даты",
"SettingsEnableColorImpairedMode": "Включить режим для людей с ограниченным цветовым восприятием",
"SettingsEnableColorImpairedModeHelpText": "Изменённый стиль позволяет пользователям с нарушениями цветового восприятия лучше различать цветовую информацию",
"SettingsLongDateFormat": "Расширенный формат даты",
"SettingsTimeFormat": "Формат времени",
"ShowAdvanced": "Показать расширенные",
"ShowSearch": "Показать поиск",
"ShowSearchHelpText": "Показать копку поиска по наведению",
"ShowSearchHelpText": "Показать копку поиска при наведении",
"Shutdown": "Выключить",
"Sort": "Сортировка",
"Source": "Источник",
"SSLCertPassword": "Пароль SSL сертификата",
"SSLCertPasswordHelpText": "Пароль pfx файла",
"SSLCertPath": "Путь SSL сертификата",
"SSLCertPathHelpText": "Путь к pfx файлу",
"Source": "Исходный код",
"SSLCertPassword": "Пароль сертификата SSL",
"SSLCertPasswordHelpText": "Пароль файла pfx",
"SSLCertPath": "Путь к сертификату SSL",
"SSLCertPathHelpText": "Путь к файлу pfx",
"StartTypingOrSelectAPathBelow": "Начните вводить или выберите путь ниже",
"StartupDirectory": "Каталог автозагрузки",
"StartupDirectory": "Каталог автозапуска",
"Status": "Статус",
"Style": "Стиль",
"SuggestTranslationChange": "Предложить изменение перевода",
"SystemTimeCheckMessage": "Расхождение системного времени более чем на 1 день. Запланированные задачи могут работать некорректно, пока не будет исправлено время",
"TableOptionsColumnsMessage": "Выберите, какие столбцы отображаются и в каком порядке",
"TagIsNotUsedAndCanBeDeleted": "Тег не используется и может быть удален",
"TagsHelpText": "Применимо к фильмам с хотя бы одним подходящим тегом",
"SystemTimeCheckMessage": "Системное время отклонилось более чем на 1 день. Запланированные задания могут не работать правильно, пока время не будет исправлено",
"TableOptionsColumnsMessage": "Выберите видимые столбцы и порядок их отображения",
"TagIsNotUsedAndCanBeDeleted": "Тег не используется и может быть удалён",
"TagsHelpText": "Применяется к индексаторам, имеющим хотя бы один совпадающий тег",
"TagsSettingsSummary": "Посмотрите все теги и способы их использования. Неиспользуемые теги можно удалить",
"Tasks": "Задачи",
"Test": "Тест",
"TestAll": "Тестировать все",
"TestAllClients": "Тестировать всех клиентов",
"TestAllClients": "Тестировать все клиенты",
"Time": "Время",
"Title": "Название",
"Tomorrow": "Завтра",
"Torrent": "Торренты",
"Torrent": "Торрент",
"Torrents": "Торренты",
"Type": "Тип",
"UI": "Пользовательский интерфейс",
"UILanguage": "Язык пользовательского интерфейса",
"UILanguageHelpTextWarning": "Требуется перезагрузка браузера",
"UISettings": "Настройки пользовательского интерфейса",
"UnableToAddANewApplicationPleaseTryAgain": "Невозможно добавить новое уведомление, попробуйте еще раз.",
"UnableToAddANewAppProfilePleaseTryAgain": "Не удалось добавить новый профиль качества. Повторите попытку.",
"UnableToAddANewDownloadClientPleaseTryAgain": "Не удалось добавить новый клиент загрузки, попробуйте еще раз.",
"UnableToAddANewIndexerPleaseTryAgain": "Не удалось добавить новый индексатор, повторите попытку.",
"UnableToAddANewIndexerProxyPleaseTryAgain": "Не удалось добавить новый индексатор, повторите попытку.",
"UnableToAddANewNotificationPleaseTryAgain": "Невозможно добавить новое уведомление, попробуйте еще раз.",
"UnableToLoadBackups": "Невозможно загрузить резервные копии",
"DownloadClientsLoadError": "Невозможно загрузить загрузчики",
"UnableToLoadGeneralSettings": "Невозможно загрузить общие настройки",
"UnableToLoadNotifications": "Невозможно загрузить уведомления",
"UnableToLoadUISettings": "Не удалось загрузить настройки пользовательского интерфейса",
"UnableToAddANewApplicationPleaseTryAgain": "Не удалось добавить новое приложение, попробуйте ещё раз.",
"UnableToAddANewAppProfilePleaseTryAgain": "Не удалось добавить новый профиль приложения, попробуйте ещё раз.",
"UnableToAddANewDownloadClientPleaseTryAgain": "Не удалось добавить новый клиент загрузки, попробуйте ещё раз.",
"UnableToAddANewIndexerPleaseTryAgain": "Не удалось добавить новый индексатор, попробуйте ещё раз.",
"UnableToAddANewIndexerProxyPleaseTryAgain": "Не удалось добавить новый прокси индексатора, попробуйте ещё раз.",
"UnableToAddANewNotificationPleaseTryAgain": "Не удалось добавить новое уведомление, попробуйте ещё раз.",
"UnableToLoadBackups": "Не удалось загрузить резервные копии",
"DownloadClientsLoadError": "Не удалось загрузить клиенты загрузки",
"UnableToLoadGeneralSettings": "Не удалось загрузить общие настройки",
"UnableToLoadNotifications": "Не удалось загрузить уведомления",
"UnableToLoadUISettings": "Не удалось загрузить настройки интерфейса",
"UnsavedChanges": "Несохраненные изменения",
"UnselectAll": "Снять все выделения",
"UpdateAutomaticallyHelpText": "Автоматически загружать и устанавливать обновления. Вы так же можете установить в Система: Обновления",
"UpdateStartupNotWritableHealthCheckMessage": "Невозможно установить обновление так как загрузочная папка '{startupFolder}' недоступна для записи для пользователя '{userName}'.",
"UpdateStartupTranslocationHealthCheckMessage": "Не удается установить обновление, поскольку папка автозагрузки \"{startupFolder}\" находится в папке перемещения приложений.",
"UpdateUiNotWritableHealthCheckMessage": "Невозможно установить обновление так как UI папка '{uiFolder}' недоступна для записи для пользователя '{userName}'.",
"UpdateScriptPathHelpText": "Путь к пользовательскому скрипту, который обрабатывает остатки после процесса обновления",
"UnselectAll": "Снять выделение со всех",
"UpdateAutomaticallyHelpText": "Автоматически загружать и устанавливать обновления Вы по-прежнему сможете выполнить установку из раздела Система: Обновления",
"UpdateStartupNotWritableHealthCheckMessage": "Невозможно установить обновление, так как каталог автозагрузки '{startupFolder}' недоступен для записи для пользователя '{userName}'.",
"UpdateStartupTranslocationHealthCheckMessage": "Невозможно установить обновление, так как каталог автозагрузки '{startupFolder}' находится в каталоге перемещения приложений.",
"UpdateUiNotWritableHealthCheckMessage": "Невозможно установить обновление, так как каталог интерфейса '{uiFolder}' недоступен для записи для пользователя '{userName}'.",
"UpdateScriptPathHelpText": "Путь к пользовательскому скрипту, который извлекает пакет обновления и обрабатывает оставшуюся часть процесса обновления",
"Uptime": "Время работы",
"URLBase": "Базовый URL",
"UrlBaseHelpText": "Для поддержки обратного прокси, по умолчанию пусто",
"URLBase": "Базовый URL-адрес",
"UrlBaseHelpText": "Для поддержки обратного прокси, значение по умолчанию - пустая строка",
"UseProxy": "Использовать прокси",
"Username": "Пользователь",
"Username": "Имя пользователя",
"Version": "Версия",
"View": "Просмотр",
"AddingTag": "Добавить тэг",
"SSLPort": "SSL порт",
"UILanguageHelpText": "Язык, который {appName} будет использовать для пользовательского интерфейса",
"EnableSslHelpText": " Требуется перезапуск от администратора",
"AddingTag": "Добавление тега",
"SSLPort": "Порт SSL",
"UILanguageHelpText": "Язык, используемый {appName} для интерфейса пользователя",
"EnableSslHelpText": " Для применения изменений требуется перезапуск с правами администратора",
"ErrorLoadingContents": "Ошибка при загрузке контента",
"Events": "События",
"EventType": "Тип события",
"Exception": "Исключение",
"FeatureRequests": "Будущие запросы",
"FeatureRequests": "Запросы функций",
"Warn": "Предупреждение",
"Wiki": "Wiki",
"YesCancel": "Да, отменить",
"YesCancel": "Да, Отмена",
"Yesterday": "Вчера",
"NotificationTriggers": "Триггеры уведомления",
"ApplicationStatusCheckAllClientMessage": "Все листы недоступны из-за ошибок",
"ApplicationStatusCheckAllClientMessage": "Все приложения недоступны из-за ошибок",
"Automatic": "Автоматически",
"DeleteApplicationMessageText": "Вы уверены, что хотите удалить уведомление '{0}'?",
"DeleteDownloadClient": "Удалить программу для скачивания",
"DeleteApplicationMessageText": "Вы уверены, что хотите удалить приложение '{name}'?",
"DeleteDownloadClient": "Удалить клиент загрузки",
"EnableInteractiveSearchHelpText": "Будет использовано при интерактивном поиске",
"Error": "Ошибка",
"NoChange": "Нет изменений",
"Age": "Возраст",
"All": "Все",
"AllIndexersHiddenDueToFilter": "Все фильмы спрятаны в соответствии с фильтром.",
"AppDataDirectory": "Директория AppData",
"AllIndexersHiddenDueToFilter": "Все индексаторы скрыты применённым фильтром.",
"AppDataDirectory": "Каталог AppData",
"Reddit": "Reddit",
"System": "Система",
"TableOptions": "Опции таблицы",
"TagCannotBeDeletedWhileInUse": "Невозможно удалить во время использования",
"TableOptions": "Параметры таблицы",
"TagCannotBeDeletedWhileInUse": "Удаление невозможно во время использования",
"Tags": "Теги",
"ApplicationStatusCheckSingleClientMessage": "Листы недоступны из-за ошибок: {0}",
"ApplicationStatusCheckSingleClientMessage": "Приложения недоступны из-за ошибок: {0}",
"EditIndexer": "Редактировать индексатор",
"MaintenanceRelease": "Техническая версия: исправлены ошибки и другие улучшения. Дополнительную информацию см. в истории коммитов Github",
"MaintenanceRelease": "Технический релиз: исправление ошибок и другие улучшения. Подробнее см. в истории коммитов Github.",
"Filters": "Фильтры",
"HistoryCleanupDaysHelpText": "Установите 0, чтобы отключить автоматическую очистку",
"HistoryCleanupDaysHelpTextWarning": "Файлы в корзине старше указанного количества дней будут очищены автоматически",
"OnApplicationUpdateHelpText": "О обновлении приложения",
"OnGrab": "При захвате",
"OnHealthIssue": "О проблемах в системе",
"HistoryCleanupDaysHelpTextWarning": "Элементы истории, старше указанного количества дней, будут автоматически удалены",
"OnApplicationUpdateHelpText": "При обновлении приложения",
"OnGrab": "При захвате релиза",
"OnHealthIssue": "При проблемах с состоянием",
"OnApplicationUpdate": "При обновлении приложения",
"TestAllIndexers": "Тестировать все индексаторы",
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent, представленный приложением, который вызывает API",
"NotificationTriggersHelpText": "Выберите, какие события должны вызвать это уведомление",
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent, предоставленный приложением, вызвавшим API",
"NotificationTriggersHelpText": "Выберите события, которые должны вызвать это уведомление",
"NetCore": ".NET",
"GrabReleases": "Захватить релиз",
"GrabReleases": "Захватить релиз(ы)",
"UnableToLoadIndexers": "Не удалось загрузить индексаторы",
"Link": "Ссылки",
"MappedDrivesRunningAsService": "Подключённые сетевые диски недоступны, если программа запущена как сервис. Обратитесь в FAQ за дополнительной информацией",
"Link": "Ссылка",
"MappedDrivesRunningAsService": "Сопоставленные сетевые диски недоступны при запуске в качестве службы Windows. См. FAQ для получения дополнительной информации",
"No": "Нет",
"Yes": "Да",
"AddRemoveOnly": "Добавить и Удалить Только",
"AddNewIndexer": "Добавить Новый Индексатор",
"AddToDownloadClient": "Добавить выпуск в Загрузочный клиент",
"AddedToDownloadClient": "Выпуск добавлен в клиент",
"AddDownloadClientToProwlarr": "Добавление клиента загрузки позволяет {appName} отправлять выпуски прямо из пользовательского интерфейса, выполняя поиск вручную.",
"AddIndexerProxy": "Добавить индексатор прокси",
"Connect": "Оповещения",
"Notification": "Оповещения",
"AddRemoveOnly": "Добавить и удалить только",
"AddNewIndexer": "Добавить новый индексатор",
"AddToDownloadClient": "Добавить релиз в клиент загрузки",
"AddedToDownloadClient": "Релиз добавлен в клиент",
"AddDownloadClientToProwlarr": "Добавление клиента загрузки позволяет {appName} отправлять релизы напрямую из пользовательского интерфейса, во время ручного поиска.",
"AddIndexerProxy": "Добавить прокси для индексатора",
"Connect": "Уведомления",
"Notification": "Уведомление",
"Encoding": "Кодирование",
"Applications": "Приложения",
"Application": "Приложения",
"Notifications": "Оповещения",
"Notifications": "Уведомления",
"InstanceName": "Имя экземпляра",
"InstanceNameHelpText": "Имя экземпляра на вкладке и имя приложения системного журнала",
"Started": "Запущено",
"Database": "База данных",
"Duration": "Длительность",
"ApplicationLongTermStatusCheckSingleClientMessage": "Все индексаторы недоступны из-за ошибок за последние 6 часов: {0}",
"Ended": "Завершен",
"ApplicationLongTermStatusCheckSingleClientMessage": "Все приложения недоступны из-за ошибок более 6 часов: {0}",
"Ended": "Завершён",
"LastExecution": "Последнее выполнение",
"NextExecution": "Следующее выполнение",
"NextExecution": "Следующий запуск",
"Queued": "В очереди",
"ApplicationLongTermStatusCheckAllClientMessage": "Все индексаторы недоступны из-за ошибок за последние 6 часов",
"ApplicationLongTermStatusCheckAllClientMessage": "Все приложения недоступны из-за ошибок более 6 часов",
"LastDuration": "Последняя длительность",
"ThemeHelpText": "Измените тему пользовательского интерфейса приложения, тема «Авто» будет использовать тему вашей ОС для установки светлого или темного режима. Вдохновленный Theme.Park",
"ThemeHelpText": "Изменить тему интерфейса приложения. Тема 'Авто' будет использовать тему вашей ОС для выбора светлого или тёмного режима. Вдохновлено {inspiredBy}.",
"Remove": "Удалить",
"Replace": "Заменить",
"TheLatestVersionIsAlreadyInstalled": "Последняя версия {appName} уже установлена",
@@ -356,180 +356,433 @@
"ApplicationUrlHelpText": "Внешний URL-адрес этого приложения, включая http(s)://, порт и базовый URL-адрес",
"Label": "Метка",
"ApplyChanges": "Применить изменения",
"ApplyTagsHelpTextRemove": "Удалить: удалить введенные теги",
"DeleteSelectedApplicationsMessageText": "Вы уверены что хотите удалить индексер '{0}'?",
"ApplyTagsHelpTextRemove": "Удалить: Удалить введённые теги",
"DeleteSelectedApplicationsMessageText": "Вы уверены, что хотите удалить выбранные приложения: {count}?",
"DeleteSelectedDownloadClients": "Удалить клиент(ы) загрузки",
"DeleteSelectedDownloadClientsMessageText": "Вы уверены, что хотите удалить {count} выбранных клиента загрузки?",
"DeleteSelectedIndexersMessageText": "Вы уверены, что хотите удалить {count} выбранных индексатора?",
"DownloadClientPriorityHelpText": "Установите приоритет нескольких клиентов загрузки. Круговой алгоритм используется для клиентов с таким же приоритетом.",
"DeleteSelectedDownloadClientsMessageText": "Вы уверены, что хотите удалить выбранные клиенты загрузки: {count}?",
"DeleteSelectedIndexersMessageText": "Вы уверены, что хотите удалить выбранные индексаторы: {count}?",
"DownloadClientPriorityHelpText": "Установить приоритет для нескольких клиентов загрузки. Клиенты с одинаковым приоритетом обрабатываются по круговой системе.",
"EditSelectedDownloadClients": "Редактировать выбранные клиенты загрузки",
"ApiKeyValidationHealthCheckMessage": "Пожалуйста, обновите свой ключ API, чтобы он был длиной не менее {length} символов. Вы можете сделать это через настройки или файл конфигурации",
"Genre": "Жанры",
"ApiKeyValidationHealthCheckMessage": "Пожалуйста, обновите свой ключ API, чтобы он был длиной не менее {length} символов в длину. Вы можете сделать это через настройки или файл конфигурации",
"Genre": "Жанр",
"Theme": "Тема",
"Year": "Год",
"ApplyTagsHelpTextAdd": "Добавить: Добавьте теги в существующий список тегов",
"ApplyTagsHelpTextHowToApplyApplications": "Как добавить ярлыки к выбранным фильмам",
"ApplyTagsHelpTextAdd": "Добавить: Добавить теги к существующему списку тегов",
"ApplyTagsHelpTextHowToApplyApplications": "Как применить теги к выбранным приложениям",
"ApplyTagsHelpTextHowToApplyIndexers": "Как применить теги к выбранным индексаторам",
"ApplyTagsHelpTextReplace": "Заменить: заменить теги введенными тегами (оставьте поле пустым, чтобы удалить все теги)",
"Track": "След",
"UpdateAvailableHealthCheckMessage": "Доступно новое обновление",
"More": "Более",
"ApplyTagsHelpTextReplace": "Заменить: Заменить теги введёнными тегами (оставьте поле пустым, чтобы удалить все теги)",
"Track": "Трек",
"UpdateAvailableHealthCheckMessage": "Доступно новое обновление: {version}",
"More": "Ещё",
"Publisher": "Издатель",
"ConnectionLostReconnect": "{appName} попытается соединиться автоматически или нажмите кнопку внизу.",
"ConnectionLostReconnect": "{appName} попытается соединиться автоматически или нажмите кнопку ниже.",
"ConnectionLostToBackend": "{appName} потерял связь с сервером и его необходимо перезагрузить, чтобы восстановить работоспособность.",
"RecentChanges": "Последние изменения",
"WhatsNew": "Что нового?",
"minutes": "Минуты",
"DeleteAppProfileMessageText": "Вы действительно хотите удалить профиль качества {0}",
"minutes": "минуты",
"DeleteAppProfileMessageText": "Вы уверены, что хотите удалить профиль приложения '{name}'?",
"EditDownloadClientImplementation": "Редактировать клиент загрузки - {implementationName}",
"EditIndexerImplementation": "Редактировать индексатор - {implementationName}",
"NoIndexersFound": "Индексаторы не найдены",
"AuthenticationRequiredHelpText": "Отредактируйте, для каких запросов требуется аутентификация. Не меняйте, пока не поймете все риски.",
"AuthenticationRequiredHelpText": "Отредактируйте, для каких запросов требуется авторизация. Не изменяйте, если не понимаете риски.",
"AuthenticationRequired": "Требуется авторизация",
"CountDownloadClientsSelected": "{count} выбранных клиентов загрузки",
"CountIndexersSelected": "{count} выбранных индексаторов",
"CountDownloadClientsSelected": "Выбрано клиентов загрузки: {count}",
"CountIndexersSelected": "Выбрано индексаторов: {count}",
"EditSelectedIndexers": "Редактировать выбранный индексатор",
"OnHealthRestored": "При восстановлении работоспособности",
"OnHealthRestoredHelpText": "При восстановлении работоспособности",
"OnHealthRestored": "При восстановлении состояния",
"OnHealthRestoredHelpText": "При восстановлении состояния",
"Implementation": "Реализация",
"NoDownloadClientsFound": "Клиенты для загрузки не найдены",
"NoDownloadClientsFound": "Клиенты загрузки не найдены",
"IndexerDownloadClientHealthCheckMessage": "Индексаторы с недопустимыми клиентами загрузки: {indexerNames}.",
"StopSelecting": "Прекратить выбор",
"StopSelecting": "Отменить выбор",
"AddDownloadClientImplementation": "Добавить клиент загрузки - {implementationName}",
"AddConnection": "Добавить подключение",
"AddConnectionImplementation": "Добавить подключение - {implementationName}",
"ManageDownloadClients": "Менеджер клиентов загрузки",
"NotificationStatusAllClientHealthCheckMessage": "Все уведомления недоступны из-за сбоев",
"NotificationStatusSingleClientHealthCheckMessage": "Уведомления недоступны из-за сбоев: {notificationNames}",
"ManageDownloadClients": "Управление клиентами загрузки",
"NotificationStatusAllClientHealthCheckMessage": "Все уведомления недоступны из-за ошибок",
"NotificationStatusSingleClientHealthCheckMessage": "Уведомления недоступны из-за ошибок: {notificationNames}",
"AddIndexerImplementation": "Добавить индексатор - {implementationName}",
"AddIndexerProxyImplementation": "Добавить индексатор - {implementationName}",
"EditConnectionImplementation": "Добавить соединение - {implementationName}",
"EditIndexerProxyImplementation": "Редактировать индексатор - {implementationName}",
"AddIndexerProxyImplementation": "Добавить прокси для индексатора - {implementationName}",
"EditConnectionImplementation": "Редактировать соединение - {implementationName}",
"EditIndexerProxyImplementation": "Редактировать прокси индексатора - {implementationName}",
"Season": "Сезон",
"AddApplicationImplementation": "Добавить соединение - {implementationName}",
"EditApplicationImplementation": "Редактировать уведомление - {implementationName}",
"AuthBasic": "Базовый (всплывающее окно браузера)",
"AddApplicationImplementation": "Добавить приложение - {implementationName}",
"EditApplicationImplementation": "Редактировать приложение - {implementationName}",
"AuthBasic": "Базовый (Всплывающее окно браузера)",
"AuthForm": "Формы (Страница авторизации)",
"DisabledForLocalAddresses": "Отключено для локальных адресов",
"None": "Ничто",
"ResetAPIKeyMessageText": "Вы уверены, что хотите сбросить Ваш API ключ?",
"None": "Ничего",
"ResetAPIKeyMessageText": "Вы уверены, что хотите сбросить ключ API?",
"Categories": "Категории",
"Album": "альбом",
"Album": "Альбом",
"AddCustomFilter": "Добавить специальный фильтр",
"AuthenticationMethod": "Способ авторизации",
"AuthenticationRequiredPasswordHelpTextWarning": "Введите новый пароль",
"AuthenticationRequiredUsernameHelpTextWarning": "Введите новое имя пользователя",
"RestartProwlarr": "Перезапустить {appName}",
"AuthenticationRequiredWarning": "Чтобы предотвратить удаленный доступ без авторизации, {appName} теперь требует, чтобы авторизация была включена. При желании вы можете отключить авторизацию с локальных адресов.",
"AuthenticationRequiredWarning": "Чтобы предотвратить удалённый доступ без авторизации, {appName} теперь требует включения авторизации. Вы можете опционально отключить авторизацию для локальных адресов.",
"Id": "ID",
"ManageClients": "Управление клиентами",
"CountApplicationsSelected": "{count} коллекция(ий) выбрано",
"CountApplicationsSelected": "Выбрано приложений: {count}",
"IndexerHDBitsSettingsCodecs": "Кодеки",
"IndexerHDBitsSettingsMediums": "Средний",
"IndexerHDBitsSettingsMediums": "Mediums",
"Directory": "Каталог",
"CustomFilter": "Настраиваемые фильтры",
"CustomFilter": "Настраиваемый фильтр",
"GrabRelease": "Захватить релиз",
"ManualGrab": "Ручной захват",
"OverrideAndAddToDownloadClient": "Замена и добавление в очередь загрузки",
"OverrideAndAddToDownloadClient": "Переопределить и добавить в клиент загрузки",
"OverrideGrabModalTitle": "Переопределить и захватить - {title}",
"PrioritySettings": "Приоритет: {priority}",
"SelectDownloadClientModalTitle": "{modalTitle} - Выберите клиент для загрузки",
"ProxyValidationBadRequest": "Не удалось проверить прокси. Код: {statusCode}",
"SelectDownloadClientModalTitle": "{modalTitle} - Выберите клиент загрузки",
"ProxyValidationBadRequest": "Не удалось протестировать прокси. Код состояния: {statusCode}",
"Default": "По умолчанию",
"AppUpdated": "{appName} обновлен",
"AppUpdatedVersion": "Приложение {appName} обновлено до версии `{version}`. Чтобы получить последние изменения, вам необходимо перезагрузить приложение {appName}",
"AppUpdated": "{appName} обновлён",
"AppUpdatedVersion": "{appName} обновлён до версии `{version}`, для получения последних изменений необходимо перезагрузить {appName}",
"Episode": "Эпизод",
"Donate": "Пожертвовать",
"DownloadClientFreeboxSettingsAppTokenHelpText": "Токен приложения, полученный при создании доступа к API Freebox (т. е. 'app_token')",
"DownloadClientPneumaticSettingsNzbFolder": "Nzb папка",
"DownloadClientPneumaticSettingsStrmFolder": "Strm папка",
"DownloadClientFreeboxSettingsAppTokenHelpText": "Токен приложения, полученный при создании доступа к Freebox API (например, 'app_token')",
"DownloadClientPneumaticSettingsNzbFolder": "Каталог NZB",
"DownloadClientPneumaticSettingsStrmFolder": "Каталог STRM",
"DownloadClientQbittorrentSettingsContentLayout": "Макет контента",
"DownloadClientQbittorrentSettingsSequentialOrderHelpText": "Скачать в последовательном порядке (qBittorrent 4.1.0+)",
"DownloadClientQbittorrentSettingsSequentialOrderHelpText": "Загружать последовательно (qBittorrent 4.1.0+)",
"DownloadClientRTorrentSettingsUrlPath": "URL-путь",
"DownloadClientRTorrentSettingsDirectoryHelpText": "Опциональное место для загрузок. Оставьте пустым, чтобы использовать каталог rTorrent по умолчанию",
"DownloadClientSettingsAddPaused": "Добавить приостановленное",
"DownloadClientRTorrentSettingsDirectoryHelpText": "Необязательное место для сохранения загрузок, оставьте поле пустым, чтобы использовать расположение rTorrent по умолчанию",
"DownloadClientSettingsAddPaused": "Добавить приостановленные",
"DownloadClientSettingsInitialState": "Начальное состояние",
"DownloadClientTransmissionSettingsUrlBaseHelpText": "Добавляет префикс к URL-адресу RPC {clientName}, например {url}, по умолчанию — '{defaultUrl}'",
"NotificationsTelegramSettingsIncludeAppName": "Включить {appName} в заголовок",
"Category": "Категория",
"Clone": "Клонировать",
"DefaultNameCopiedProfile": "{name} - Копировать",
"DownloadClientFreeboxSettingsHostHelpText": "Имя хоста или IP-адрес хоста Freebox, по умолчанию — '{url}' (будет работать только в той же сети)",
"DownloadClientFreeboxSettingsHostHelpText": "Имя хоста или IP-адрес хоста Freebox, по умолчанию — '{url}' (будет работать только если находится в одной сети)",
"DownloadClientFreeboxSettingsPortHelpText": "Порт, используемый для доступа к интерфейсу Freebox, по умолчанию — '{port}'",
"DownloadClientPneumaticSettingsNzbFolderHelpText": "Эта папка должна быть доступна из XBMC",
"DownloadClientQbittorrentSettingsSequentialOrder": "Последовательный порядок",
"DownloadClientSettingsDestinationHelpText": "Вручную указывает место назначения загрузки. Оставьте поле пустым, чтобы использовать значение по умолчанию",
"DownloadClientSettingsInitialStateHelpText": "Исходное состояние торрентов, добавленных в {clientName}",
"DownloadClientPneumaticSettingsNzbFolderHelpText": "Этот каталог должен быть доступен из XBMC",
"DownloadClientQbittorrentSettingsSequentialOrder": "Загружать последовательно",
"DownloadClientSettingsDestinationHelpText": "Ручная установка места загрузки, оставьте пустым для использования значения по умолчанию",
"DownloadClientSettingsInitialStateHelpText": "Начальное состояние для торрентов, добавленных в {clientName}",
"DownloadClientRTorrentSettingsAddStopped": "Добавить остановленные",
"External": "Внешний",
"Destination": "Место назначения",
"BlackholeFolderHelpText": "Папка, в которой {appName} будет хранить файл {extension}",
"DownloadClientDownloadStationSettingsDirectoryHelpText": "Опциональная общая папка для размещения загрузок. Оставьте пустым, чтобы использовать каталог Download Station по умолчанию",
"BlackholeFolderHelpText": "Каталог, в котором {appName} будет хранить файл {extension}",
"DownloadClientDownloadStationSettingsDirectoryHelpText": "Необязательный общий каталог для сохранения загрузок, оставьте поле пустым, чтобы использовать расположение Download Station по умолчанию",
"DownloadClientFloodSettingsAdditionalTags": "Дополнительные теги",
"DownloadClientFloodSettingsUrlBaseHelpText": "Добавляет префикс к Flood API, например {url}",
"DownloadClientFloodSettingsUrlBaseHelpText": "Добавляет префикс к Flood API, такой как {url}",
"DownloadClientFreeboxSettingsApiUrl": "URL-адрес API",
"DownloadClientFreeboxSettingsApiUrlHelpText": "Определите базовый URL-адрес Freebox API с версией API, например '{url}', по умолчанию — '{defaultApiUrl}'",
"DownloadClientFreeboxSettingsAppId": "Идентификатор приложения",
"DownloadClientFreeboxSettingsAppIdHelpText": "Идентификатор приложения, указанный при создании доступа к Freebox API (т. е. 'app_id')",
"DownloadClientFreeboxSettingsAppId": "ID приложения",
"DownloadClientFreeboxSettingsAppIdHelpText": "ID приложения, полученный при создании доступа к Freebox API (например, 'app_id')",
"DownloadClientFreeboxSettingsAppToken": "Токен приложения",
"DownloadClientFloodSettingsAdditionalTagsHelpText": "Добавляет свойства мультимедиа в виде тегов. Подсказки являются примерами.",
"DownloadClientNzbgetSettingsAddPausedHelpText": "Для этой опции требуется как минимум NzbGet версии 16.0",
"DownloadClientPneumaticSettingsStrmFolderHelpText": "Файлы .strm в этой папке будут импортированы дроном",
"DownloadClientQbittorrentSettingsFirstAndLastFirst": "Первый и последний Первый",
"DownloadClientQbittorrentSettingsFirstAndLastFirstHelpText": "Сначала скачайте первую и последнюю части (qBittorrent 4.1.0+)",
"DownloadClientNzbgetSettingsAddPausedHelpText": "Для работы этого параметра требуется версия NzbGet не ниже 16.0",
"DownloadClientPneumaticSettingsStrmFolderHelpText": "Файлы .strm в этом каталоге будут импортированы дроном",
"DownloadClientQbittorrentSettingsFirstAndLastFirst": "Первое и последнее сначала",
"DownloadClientQbittorrentSettingsFirstAndLastFirstHelpText": "Загружать первые и последние части сначала (qBittorrent 4.1.0+)",
"UseSsl": "Использовать SSL",
"AuthenticationMethodHelpTextWarning": "Пожалуйста, выберите действительный метод аутентификации",
"AuthenticationMethodHelpTextWarning": "Пожалуйста, выберите допустимый метод авторизации",
"XmlRpcPath": "Путь XML RPC",
"UsenetBlackholeNzbFolder": "Nzb папка",
"UsenetBlackholeNzbFolder": "Каталог NZB",
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Подтвердите новый пароль",
"DownloadClientAriaSettingsDirectoryHelpText": "Опциональное местоположение для загрузок. Оставьте пустым, чтобы использовать местоположение Aria2 по умолчанию",
"DownloadClientSettingsUrlBaseHelpText": "Добавляет префикс к URL-адресу {clientName}, например {url}",
"DownloadClientAriaSettingsDirectoryHelpText": "Необязательное место для сохранения загрузок, оставьте поле пустым, чтобы использовать расположение Aria2 по умолчанию",
"DownloadClientSettingsUrlBaseHelpText": "Добавляет префикс к URL-адресу {clientName}, например, {url}",
"NoHistoryFound": "История не найдена",
"DownloadClientFloodSettingsTagsHelpText": "Начальные теги загрузки. Чтобы быть распознанным, загрузка должна иметь все начальные теги. Это позволяет избежать конфликтов с несвязанными загрузками.",
"DownloadClientQbittorrentSettingsContentLayoutHelpText": "Использовать ли настроенный макет контента qBittorrent, исходный макет из торрента или всегда создавать подпапку (qBittorrent 4.3.2+)",
"DownloadClientQbittorrentSettingsInitialStateHelpText": "Исходное состояние торрентов, добавленных в qBittorrent. Обратите внимание, что принудительные торренты не подчиняются ограничениям на раздачу",
"DownloadClientDelugeSettingsUrlBaseHelpText": "Добавляет префикс к URL-адресу json deluge, см. {url}",
"NotificationsTelegramSettingsIncludeAppNameHelpText": "При необходимости добавьте к заголовку сообщения префикс {appName}, чтобы отличать уведомления от разных приложений",
"DownloadClientQbittorrentSettingsUseSslHelpText": "Используйте безопасное соединение. См. «Параметры» -> «Веб-интерфейс» -> «Использовать HTTPS вместо HTTP» в qBittorrent.",
"DownloadClientRTorrentSettingsAddStoppedHelpText": "Включение добавит торренты и магниты в rTorrent в остановленном состоянии. Это может привести к поломке магнет файлов.",
"DownloadClientRTorrentSettingsUrlPathHelpText": "Путь к конечной точке XMLRPC см. в {url}. Обычно это RPC2 или [путь к ruTorrent]{url2} при использовании ruTorrent.",
"DownloadClientSettingsUseSslHelpText": "Использовать безопасное соединение при подключении к {clientName}",
"DownloadClientTransmissionSettingsDirectoryHelpText": "Опциональное место для загрузок. Оставьте пустым, чтобы использовать каталог Transmission по умолчанию",
"DownloadClientFloodSettingsTagsHelpText": "Начальные теги загрузки. Чтобы быть распознанным, загрузка должна иметь все начальные теги. Это предотвращает конфликты с несвязанными загрузками.",
"DownloadClientQbittorrentSettingsContentLayoutHelpText": "Выбрать расположение контента: настроенное в qBittorrent, исходный макет из торрента или всегда создавать подкаталог (qBittorrent 4.3.2+)",
"DownloadClientQbittorrentSettingsInitialStateHelpText": "Исходное состояние торрентов, добавленных в qBittorrent. Обратите внимание, что принудительные торренты не соблюдают ограничения на раздачу",
"DownloadClientDelugeSettingsUrlBaseHelpText": "Добавляет префикс к URL-адресу json Deluge, см.: {url}",
"NotificationsTelegramSettingsIncludeAppNameHelpText": "При необходимости добавить к заголовку сообщения префикс {appName}, чтобы различать уведомления от разных приложений",
"DownloadClientQbittorrentSettingsUseSslHelpText": "Использовать защищённое соединение. Смотрите 'Параметры' -> 'Веб-интерфейс' -> 'Использовать HTTPS вместо HTTP' в qBittorrent.",
"DownloadClientRTorrentSettingsAddStoppedHelpText": "Включение добавит торренты и магнет-ссылки в rTorrent в остановленном состоянии. Это может привести к повреждению магнет-файлов.",
"DownloadClientRTorrentSettingsUrlPathHelpText": "Путь к конечной точке XMLRPC см. {url}. Обычно это RPC2 или [путь к ruTorrent]{url2} при использовании ruTorrent.",
"DownloadClientSettingsUseSslHelpText": "Использовать защищённое соединение при подключении к {clientName}",
"DownloadClientTransmissionSettingsDirectoryHelpText": "Необязательное место для сохранения загрузок, оставьте поле пустым, чтобы использовать расположение Transmission по умолчанию",
"IndexerSettingsAdditionalParameters": "Дополнительные параметры",
"IndexerSettingsSeedRatio": "Рейтинг",
"IndexerSettingsSeedTimeHelpText": "Время, в течение которого торрент должен оставаться на раздаче перед остановкой, пусто: используется значение по умолчанию клиента загрузки",
"IndexerSettingsSeedRatio": "Коэффициент раздачи",
"IndexerSettingsSeedTimeHelpText": "Время, в течение которого торрент должен оставаться на раздаче перед остановкой, пусто используется значение клиента загрузки по умолчанию",
"PasswordConfirmation": "Подтверждение пароля",
"TorrentBlackholeTorrentFolder": "Папка торрента",
"TorrentBlackholeSaveMagnetFilesHelpText": "Сохраните магнитную ссылку, если файл .torrent недоступен (полезно только в том случае, если клиент загрузки поддерживает магниты, сохраненные в файле)",
"TorrentBlackholeTorrentFolder": "Каталог торрента",
"TorrentBlackholeSaveMagnetFilesHelpText": "Сохранить магнет-ссылку, если файл .torrent недоступен (полезно только в случае, если клиент загрузки поддерживает магнет-ссылки, сохранённые в файле)",
"NotificationsEmailSettingsUseEncryption": "Использовать шифрование",
"NotificationsEmailSettingsUseEncryptionHelpText": "Предпочитать использовать шифрование, если оно настроено на сервере, всегда использовать шифрование через SSL (только порт 465) или StartTLS (любой другой порт) или никогда не использовать шифрование",
"NotificationsEmailSettingsUseEncryptionHelpText": "Выбрать режим шифрования: предпочитать шифрование, если оно настроено на сервере; всегда использовать шифрование через SSL (только порт 465) или StartTLS (любой другой порт); никогда не использовать шифрование.",
"LabelIsRequired": "Требуется метка",
"IndexerSettingsSeedTime": "Время сидирования",
"Mixed": "Смешанный",
"IndexerSettingsApiPath": "Путь API",
"IndexerSettingsSeedRatioHelpText": "Рейтинг, которого должен достичь торрент перед остановкой, пустой использует значение по умолчанию клиента загрузки. Рейтинг должен быть не менее 1,0 и соответствовать правилам индексаторов",
"IndexerSettingsSeedRatioHelpText": "Рейтинг, которого должен достичь торрент перед остановкой, пусто используется значение по умолчанию клиента загрузки. Рейтинг должен быть не менее 1,0 и соответствовать правилам индексаторов",
"InvalidUILanguage": "В вашем пользовательском интерфейсе установлен недопустимый язык. Исправьте его и сохраните настройки",
"IndexerHDBitsSettingsCodecsHelpText": "Если не указано, используются все параметры.",
"IndexerHDBitsSettingsMediumsHelpText": "Если не указано, используются все параметры.",
"IndexerSettingsApiPathHelpText": "Путь к API, обычно {url}",
"TorrentBlackholeSaveMagnetFilesExtension": "Сохранить расширение магнет файлов",
"TorrentBlackholeSaveMagnetFiles": "Сохранить магнет файлы",
"TorrentBlackholeSaveMagnetFilesExtension": "Сохранить магнет-файлы с расширением",
"TorrentBlackholeSaveMagnetFiles": "Сохранить магнет-файлы",
"IndexerSettingsCookie": "Cookie",
"SecretToken": "Секретный токен",
"TorrentBlackholeSaveMagnetFilesExtensionHelpText": "Расширение для магнитных ссылок, по умолчанию «.magnet»",
"MinimumSeeders": "Минимум сидеров (раздающих)",
"TorrentBlackholeSaveMagnetFilesExtensionHelpText": "Расширение для магнет-ссылок, по умолчанию '.magnet'",
"MinimumSeeders": "Минимум сидеров",
"SeedTime": "Время сидирования",
"SeedRatio": "Рейтинг",
"SeedRatio": "Коэффициент раздачи",
"days": "дни",
"ApplicationSettingsSyncRejectBlocklistedTorrentHashes": "Отклонять хэши торрентов из черного списка при захвате",
"ApplicationSettingsSyncRejectBlocklistedTorrentHashes": "Отклонять хэши торрентов из чёрного списка при захвате",
"Author": "Автор",
"IndexerHDBitsSettingsOriginsHelpText": "Если не указано, используются все параметры.",
"Any": "Любой",
"ApplicationSettingsSyncRejectBlocklistedTorrentHashesHelpText": "Если торрент заблокирован хешем, он может не быть должным образом отклонен во время RSS/поиска для некоторых индексаторов. Включение этого параметра позволит отклонить его после захвата торрента, но до его отправки клиенту.",
"BuiltIn": "Встроено",
"ProxyValidationUnableToConnect": "Невозможно подключиться к индексатору: {exceptionMessage}. Подробности см. в журнале этой ошибки",
"ApplicationSettingsSyncRejectBlocklistedTorrentHashesHelpText": "Если торрент заблокирован по хэшу, он может не правильно быть отклонён во время RSS/Поиска для некоторых индексаторов.",
"BuiltIn": "Встроенный",
"ProxyValidationUnableToConnect": "Не удалось подключиться к прокси: {exceptionMessage}. Проверьте журнал ошибок для получения дополнительной информации",
"Script": "Скрипт",
"InfoUrl": "URL-адрес информации",
"PublishedDate": "Дата публикации",
"AllSearchResultsHiddenByFilter": "Все результаты скрыты фильтром"
"AllSearchResultsHiddenByFilter": "Все результаты поиска скрыты применённым фильтром.",
"HealthMessagesInfoBox": "Дополнительную информацию о причине появления этих сообщений о проверке работоспособности можно найти, перейдя по ссылке wiki (значок книги) в конце строки или проверить [журналы]({link}). Если у вас возникли трудности с пониманием этих сообщений, вы можете обратиться в нашу службу поддержки по ссылкам ниже.",
"PackageVersionInfo": "{packageVersion} создан {packageAuthor}",
"TotalUserAgentQueries": "Всего запросов User Agent",
"TotalIndexerSuccessfulGrabs": "Всего успешных захватов индексатора",
"TotalIndexerQueries": "Всего запросов к индексатору",
"TVSearchTypes": "Типы поиска ТВ-программ",
"SyncLevelAddRemove": "Только добавление и удаление: при добавлении или удалении индексаторов из {appName} это удалённое приложение будет обновлено.",
"SyncProfiles": "Профили синхронизации",
"TorznabUrl": "URL-адрес Torznab",
"TotalGrabs": "Всего захватов",
"TotalQueries": "Всего запросов",
"TotalHostGrabs": "Всего захватов с хоста",
"TestAllApps": "Тестировать все приложения",
"TotalUserAgentGrabs": "Всего захватов User Agent",
"SyncProfile": "Профиль синхронизации",
"TotalHostQueries": "Всего запросов к хосту",
"SyncLevelFull": "Полная синхронизация: приложение будет поддерживать индексаторы полностью синхронизированными. Любые изменения, внесённые в индексаторы {appName}, будут синхронизированы с этим приложением.",
"Menu": "Меню",
"Artist": "Исполнитель",
"OnGrabHelpText": "При захвате релиза",
"Book": "Книга",
"LogSizeLimit": "Ограничение размера журнала",
"LogSizeLimitHelpText": "Максимальный размер файла журнала в МБ перед архивированием. По умолчанию - 1 МБ.",
"AudioSearch": "Поиск аудио",
"MusicSearchTypes": "Типы поиска музыки",
"AreYouSureYouWantToDeleteCategory": "Вы уверены, что хотите удалить сопоставленную категорию?",
"ClearHistory": "Очистить историю",
"Privacy": "Конфиденциальность",
"EnableIndexer": "Включить индексатор",
"EnableRssHelpText": "Включить RSS-канал для индексатора",
"Stats": "Статистика",
"HistoryDetails": "Подробности истории",
"IndexerDisabled": "Индексатор выключен",
"IndexerFailureRate": "Процент неудач индексатора",
"LastFailure": "Последняя ошибка",
"MassEditor": "Пакетный редактор",
"FullSync": "Полная синхронизация",
"IndexerAuth": "Авторизация индексатора",
"AddApplication": "Добавить приложение",
"AddSyncProfile": "Добавить профиль синхронизации",
"MappedCategories": "Сопоставленные категории",
"Redirect": "Перенаправление",
"ElapsedTime": "Прошедшее время",
"FilterPlaceHolder": "Поисковые индексаторы",
"Private": "Конфиденциальный",
"AdvancedSettingsHiddenClickToShow": "Расширенные настройки скрыты, нажмите, чтобы показать",
"BasicSearch": "Базовый поиск",
"IndexerStatus": "Статус индексатора",
"ManageApplications": "Управление приложениями",
"PackSeedTime": "Время раздачи пакета",
"SearchAllIndexers": "Поиск во всех индексаторах",
"SettingsSqlLoggingHelpText": "Журналировать все SQL-запросы из {appName}",
"DeleteAppProfile": "Удалить профиль приложения",
"EditSyncProfile": "Редактировать профиль синхронизации",
"EnabledRedirected": "Включено, Перенаправлено",
"IndexerProxies": "Прокси индексатора",
"InitialFailure": "Начальный сбой",
"GoToApplication": "Перейти к приложению",
"IndexerName": "Название индексатора",
"NoSearchResultsFound": "Не обнаружены результаты поиска, повторите поиск ниже.",
"QueryOptions": "Параметры запроса",
"SearchTypes": "Типы поиска",
"DeleteIndexerProxy": "Удалить прокси индексатора",
"DeleteSelectedApplications": "Удалить выбранные приложения",
"DevelopmentSettings": "Настройки разработки",
"HistoryCleanup": "Очистка истории",
"SearchCountIndexers": "Поиск в {count} индексаторе(ах)",
"SelectIndexers": "Выберите индексаторы",
"IndexerBeyondHDSettingsApiKeyHelpText": "Ключ API с сайта (Находится в Моя безопасность => Ключ API)",
"IndexerBeyondHDSettingsSearchTypes": "Типы поиска",
"IndexerBeyondHDSettingsSearchTypesHelpText": "Выберите типы релизов, которые вас интересуют. Если ничего не выбрано, используются все варианты.",
"IndexerFileListSettingsPasskeyHelpText": "Passkey сайта (Это алфавитно-цифровая строка в URL трекера, отображаемая в вашем клиенте загрузки)",
"IndexerGazelleGamesSettingsSearchGroupNames": "Поиск названий групп",
"IndexerIPTorrentsSettingsCookieUserAgentHelpText": "User-Agent, используемый с cookie из браузера",
"IndexerIPTorrentsSettingsFreeleechOnlyHelpText": "Искать только релизы freeleech",
"AreYouSureYouWantToDeleteIndexer": "Вы уверены, что хотите удалить '{name}' из {appName}?",
"BookSearch": "Поиск книг",
"BookSearchTypes": "Типы поиска книг",
"IndexerPassThePopcornSettingsFreeleechOnlyHelpText": "Искать только релизы freeleech",
"IndexerSettingsGrabLimitHelpText": "Максимальное количество захватов, указанное соответствующей единицей, которое {appName} будет разрешать для сайта",
"IndexerSettingsLimitsUnit": "Единица лимита",
"IndexerSettingsLimitsUnitHelpText": "Единица времени для определения лимитов каждого индексатора",
"IndexerSettingsQueryLimit": "Лимит запросов",
"IndexerSettingsRssKey": "Ключ RSS",
"Parameters": "Параметры",
"RedirectHelpText": "Перенаправить входящий запрос на загрузку для индексатора и передать захват напрямую, вместо проксирования запроса через {appName}.",
"SettingsFilterSentryEvents": "Фильтровать события аналитики",
"NotSupported": "Не поддерживается",
"SyncLevel": "Уровень синхронизации",
"UISettingsSummary": "Параметры для людей с ограниченными возможностями: дата, язык и цветовая схема",
"DeleteApplication": "Удалить приложение",
"Description": "Описание",
"IndexerQuery": "Запрос индексатора",
"IndexerSite": "Сайт индексатора",
"IndexerTagsHelpText": "Используйте теги для указания прокси индексатора или приложений, синхронизированных с индексатором.",
"QueryType": "Тип запроса",
"MovieSearch": "Поиск фильма",
"RssFeed": "Лента RSS",
"AppsMinimumSeeders": "Приложения Минимальное количество сидеров",
"ConnectSettingsSummary": "Уведомления и пользовательские скрипты",
"DeleteClientCategory": "Удалить категорию клиента загрузки",
"IndexerAlreadySetup": "Как минимум один индексатор уже настроен",
"IndexerCategories": "Категории индексатора",
"IndexerProxy": "Прокси индексатора",
"IndexerRss": "RSS индексатора",
"Public": "Публичный",
"SettingsLogRotate": "Ротация журналов",
"SettingsLogRotateHelpText": "Максимальное количество сохраняемых файлов в каталоге журналов",
"DisabledUntil": "Отключено до",
"SearchType": "Тип поиска",
"AppSettingsSummary": "Приложения и настройки для управления взаимодействием {appName} с вашими программами PVR",
"SettingsFilterSentryEventsHelpText": "Фильтровать известные события ошибок пользователя, чтобы они не отправлялись в виде аналитики.",
"MovieSearchTypes": "Типы поиска фильма",
"Apps": "Программы",
"Auth": "Авторизация",
"ClearHistoryMessageText": "Вы уверены, что хотите очистить всю историю {appName}?",
"DeleteSelectedIndexer": "Удалить выбранный индексатор",
"DownloadClientCategory": "Категория клиента загрузки",
"IndexerHDBitsSettingsPasskeyHelpText": "Passkey из пользовательских данных",
"SettingsIndexerLogging": "Включить индексатор",
"RepeatSearch": "Повторить поиск",
"AddCategory": "Добавить категорию",
"AdvancedSettingsShownClickToHide": "Расширенные настройки видны, нажмите, чтобы скрыть",
"AppProfileInUse": "Профиль приложения в использовании",
"IndexerAlphaRatioSettingsExcludeScene": "Исключить SCENE",
"IndexerAlphaRatioSettingsExcludeSceneHelpText": "Исключить релизы SCENE из результатов",
"IndexerBeyondHDSettingsRewindOnly": "Только повторы",
"IndexerDetails": "Подробности индексатора",
"EditCategory": "Редактировать категорию",
"FoundCountReleases": "Найдено релизов: {itemCount}",
"IndexerAlphaRatioSettingsFreeleechOnlyHelpText": "Искать только релизы freeleech",
"IndexerBeyondHDSettingsLimitedOnly": "Только лимитированные",
"IndexerBeyondHDSettingsRefundOnlyHelpText": "Искать только возвраты",
"IndexerBeyondHDSettingsRewindOnlyHelpText": "Искать только повторы",
"IndexerHistoryLoadError": "Ошибка загрузки истории индексатора",
"IndexerNebulanceSettingsApiKeyHelpText": "Ключ API из Настроек пользователя > Ключи API. Ключ должен иметь права на просмотр и загрузку",
"IndexerPassThePopcornSettingsApiKeyHelpText": "Ключ API сайта",
"SettingsConsoleLogLevel": "Уровень журналирования консоли",
"IndexerSettingsGrabLimit": "Лимит захвата",
"IndexerSettingsVipExpiration": "Дата окончания VIP",
"SearchQueries": "Запросы поиска",
"SeedRatioHelpText": "Коэффициент, которого должен достичь торрент перед остановкой, пусто - значение по умолчанию приложения",
"NoIndexerCategories": "Нет категорий для этого индексатора",
"IndexerVipExpiredHealthCheckMessage": "Привилегии VIP для индексатора истекли: {indexerNames}",
"DefaultCategory": "Категория по умолчанию",
"IndexerHDBitsSettingsFreeleechOnlyHelpText": "Показать только релизы freeleech",
"IndexerHDBitsSettingsOrigins": "Источники",
"IndexerNzbIndexSettingsApiKeyHelpText": "Ключ API сайта",
"IndexerOrpheusSettingsApiKeyHelpText": "API ключ сайта (Находится в Настройки => Настройки доступа)",
"IndexerSettingsApiUser": "Пользователь API",
"IndexerSettingsAppsMinimumSeeders": "Приложения Минимальное количество сидеров",
"RawSearchSupported": "Поддерживается необработанный поиск",
"RssQueries": "Запросы RSS",
"SearchCapabilities": "Возможности поиска",
"SearchIndexers": "Поисковые индексаторы",
"ActiveIndexers": "Активные индексаторы",
"ApplicationsLoadError": "Не удалось загрузить список приложений",
"IncludeManualGrabsHelpText": "Включить ручные захваты, сделанные внутри {appName}",
"NoApplicationsFound": "Приложения не найдены",
"IndexerHDBitsSettingsUseFilenamesHelpText": "Выберите этот вариант, если хотите использовать имена файлов торрента в качестве названий релизов",
"IndexerHDBitsSettingsUsernameHelpText": "Имя пользователя сайта",
"IndexerSettingsCookieHelpText": "Cookie сайта",
"IndexerSettingsFreeleechOnly": "Только Freeleech",
"IndexerSettingsPasskey": "Pass Key",
"IndexerId": "ID индексатора",
"DeleteSelectedIndexers": "Удалить выбранные индексаторы",
"IndexerGazelleGamesSettingsApiKeyHelpTextWarning": "Требуются разрешения 'Пользователь' и 'Торренты'",
"IndexerNewznabSettingsAdditionalParametersHelpText": "Дополнительные параметры Newznab",
"IndexerNewznabSettingsApiKeyHelpText": "Ключ API сайта",
"IndexerSettingsBaseUrl": "Базовый URL-адрес",
"IndexerSettingsAppsMinimumSeedersHelpText": "Минимальное количество сидеров, необходимое для приложений, чтобы индексатор мог скачать, пустое значение - значение профиля синхронизации по умолчанию",
"PackSeedTimeHelpText": "Время, в течение которого торрент пакета (сезон или дискография) должен оставаться на раздаче перед остановкой, пусто — используется значение приложения по умолчанию",
"IndexerNoDefinitionCheckHealthCheckMessage": "Индексаторы {indexerNames} не имеют определения и не работают. Удалите их из {appName} и добавьте снова.",
"AuthQueries": "Запросы авторизации",
"IndexerNewznabSettingsVipExpirationHelpText": "Введите дату (yyyy-mm-dd) окончания VIP или оставьте поле пустым, {appName} оповестит вас за неделю до окончания VIP",
"IndexerPassThePopcornSettingsApiUserHelpText": "Эти настройки расположены в разделе безопасности PassThePopcorn (Редактировать профиль > Безопасность).",
"IndexerSettingsBaseUrlHelpText": "Выберите основной URL-адрес, который {appName} будет использовать для запросов на сайт",
"Open": "Открыть",
"IndexerInfo": "Информация об индексаторе",
"SettingsLogSql": "Журналировать SQL",
"AverageResponseTimesMs": "Среднее время ответа индексатора (мс)",
"CountIndexersAvailable": "Доступно индексаторов: {count}",
"IndexerSettingsSummary": "Конфигурация глобальных настроек индексатора, включая прокси.",
"IndexerTagsHelpTextWarning": "Теги следует использовать с осторожностью, они могут иметь непредвиденные последствия. Индексатор с тегом будет синхронизироваться только с приложениями, имеющими тот же тег.",
"IndexerTorrentSyndikatSettingsApiKeyHelpText": "Ключ API сайта",
"NoIndexerHistory": "Нет истории для этого индексатора",
"AppsMinimumSeedersHelpText": "Минимальное количество сидеров, необходимое для приложений, чтобы индексатор мог скачать, пустое значение - значение профиля синхронизации по умолчанию",
"DownloadClientsSettingsSummary": "Настройки клиентов загрузки для интеграции в интерфейс поиска {appName}",
"Proxies": "Прокси",
"AppProfileSelectHelpText": "Профили приложения используются для управления настройками RSS, автоматического поиска и интерактивного поиска при синхронизации приложения",
"ProwlarrSupportsAnyDownloadClient": "{appName} совместим с любым из перечисленных ниже клиентом загрузки.",
"Query": "Запрос",
"QueryResults": "Результаты запроса",
"ActiveApps": "Активные приложения",
"ApplicationTagsHelpText": "Синхронизировать индексаторы с этим приложением, которые имеют один или более совпадающих тегов. Если здесь не перечислены теги, то все индексаторы будут синхронизированы без ограничений.",
"ApplicationTagsHelpTextWarning": "Теги следует использовать с осторожностью, они могут иметь непредвиденные последствия. Приложение с тегом будет синхронизироваться только с индексаторами, имеющими тот же тег.",
"IndexerGazelleGamesSettingsApiKeyHelpText": "API ключ сайта (Находится в Настройки => Настройки доступа)",
"IndexerGazelleGamesSettingsSearchGroupNamesHelpText": "Поиск релизов по названию групп",
"IndexerHDBitsSettingsUseFilenames": "Использовать имена файлов",
"IndexerRedactedSettingsApiKeyHelpText": "API ключ сайта (Находится в Настройки => Настройки доступа)",
"IndexerSettingsPackSeedTime": "Время раздачи пакета",
"IndexerSettingsPackSeedTimeIndexerHelpText": "Время, в течение которого торрент пакета (сезон или дискография) должен оставаться на раздаче перед остановкой, пусто — используется значение приложения по умолчанию",
"ClickToChangeQueryOptions": "Нажмите, чтобы изменить параметры запроса",
"DownloadClientSettingsDefaultCategorySubFolderHelpText": "Категория по умолчанию, если для релиза нет соответствующей категории. Создание определённой категории для {appName}, позволяет избежать конфликтов с загрузками, не связанными с {appName}. Использование категории не обязательно, но настоятельно рекомендуется. Создаёт подкаталог [category] в каталоге вывода.",
"AverageQueries": "Среднее количество запросов",
"IndexerGazelleGamesSettingsFreeleechOnlyHelpText": "Искать только релизы freeleech",
"IndexerMTeamTpSettingsApiKeyHelpText": "Ключ API сайта (Находится в Панели управления пользователя => Безопасность => Лаборатория)",
"IndexerMTeamTpSettingsFreeleechOnlyHelpText": "Искать только релизы freeleech",
"ProwlarrDownloadClientsAlert": "Если вы намерены выполнять поиск непосредственно в {appName}, вам необходимо добавить клиенты загрузки. В противном случае, добавлять их здесь не нужно. Для поиска из ваших приложений используются клиенты загрузки, которые настроены в самих приложениях.",
"ProwlarrDownloadClientsInAppOnlyAlert": "Клиенты загрузки предназначены только для поиска внутри приложения {appName} и не синхронизируются с другими приложениями. Мы не планируем добавлять функцию синхронизации.",
"ProwlarrSupportsAnyIndexer": "{appName} поддерживает множество индексаторов, а также любой индексатор, использующий стандарт Newznab/Torznab, с помощью 'Generic Newznab' (для Usenet) или 'Generic Torznab' (для торрентов). Выберите и добавьте свой индексатор из списка ниже.",
"Redirected": "Перенаправлено",
"DownloadClientSettingsDefaultCategoryHelpText": "Категория по умолчанию, если для релиза нет соответствующей категории. Создание определённой категории для {appName}, позволяет избежать конфликтов с загрузками, не связанными с {appName}. Использование категории не обязательно, но настоятельно рекомендуется.",
"DownloadClientSettingsPriorityItemHelpText": "Приоритет, используемый при захвате элементов",
"GrabTitle": "Захватить название",
"IndexerBeyondHDSettingsFreeleechOnlyHelpText": "Искать только релизы freeleech",
"IndexerBeyondHDSettingsLimitedOnlyHelpText": "Искать только freeleech (Лимитированная отдача)",
"IndexerBeyondHDSettingsRefundOnly": "Только возврат",
"IndexerBeyondHDSettingsRssKeyHelpText": "Ключ RSS с сайта (Находится в Моя безопасность => Ключ RSS)",
"IndexerDownloadClientHelpText": "Определите клиент загрузки, используемый для загрузки из этого индексатора в {appName}.",
"NewznabUrl": "URL-адрес Newznab",
"SeedTimeHelpText": "Время, в течение которого торрент должен оставаться на раздаче перед остановкой, пусто — используется значение приложения по умолчанию",
"IndexerFileListSettingsFreeleechOnlyHelpText": "Искать только релизы freeleech",
"IndexerFileListSettingsUsernameHelpText": "Имя пользователя сайта",
"IndexerHealthCheckNoIndexers": "Нет включённых индексаторов, {appName} не будет возвращать результаты поиска",
"IndexerObsoleteCheckMessage": "Индексаторы: {0} устарели или были обновлены. Удалите их из {appName} и (или) добавьте снова",
"IndexerSettingsQueryLimitHelpText": "Максимальное количество запросов, указанное соответствующей единицей, которое {appName} будет разрешать для сайта",
"IndexerVipExpiringHealthCheckMessage": "Привилегии VIP для индексатора скоро истекут: {indexerNames}",
"MinimumSeedersHelpText": "Минимальное количество сидеров, необходимое приложению для захвата индексатором",
"SelectedCountOfCountReleases": "Выбрано {selectedCount} из {itemCount} релизов",
"SemiPrivate": "Частично приватный",
"SettingsIndexerLoggingHelpText": "Журналировать дополнительные данные индексатора, включая ответ",
"SyncAppIndexers": "Синхронизировать индексаторы приложения",
"UnableToLoadAppProfiles": "Не удалось загрузить профили приложения",
"Website": "Веб-сайт",
"TvSearch": "Поиск ТВ-программ",
"Url": "URL-адрес",
"UnableToLoadIndexerProxies": "Не удалось загрузить прокси индексатора",
"UnableToLoadDevelopmentSettings": "Не удалось загрузить настройки разработки",
"VipExpiration": "Дата окончания VIP",
"IndexerIPTorrentsSettingsCookieUserAgent": "Cookie User-Agent",
"AverageGrabs": "Среднее количество захватов",
"IndexerSettingsPreferMagnetUrl": "Предпочитать Magnet URL",
"IndexerPassThePopcornSettingsGoldenPopcornOnly": "Только Golden Popcorn",
"IndexerSettingsPreferMagnetUrlHelpText": "При включении этот индексатор предпочтёт использовать для загрузки magnet URL, с возможностью перехода на торрент-ссылки",
"IndexerAvistazSettingsPasswordHelpText": "Пароль веб-сайта",
"IndexerAvistazSettingsPidHelpText": "PID со страницы Мой аккаунт или Мой профиль",
"IndexerAvistazSettingsUsernameHelpTextWarning": "API этого индексатора доступен только для участников и выше рангом.",
"IndexerPassThePopcornSettingsGoldenPopcornOnlyHelpText": "Искать релизы только Golden Popcorn",
"PreferMagnetUrl": "Предпочитать Magnet URL",
"PreferMagnetUrlHelpText": "При включении этот индексатор предпочтёт использовать для загрузки magnet URL, с возможностью перехода на торрент-ссылки",
"IndexerAvistazSettingsFreeleechOnlyHelpText": "Искать только релизы freeleech",
"IndexerAvistazSettingsUsernameHelpText": "Имя пользователя сайта"
}

View File

@@ -518,7 +518,7 @@
"OnApplicationUpdateHelpText": "Uygulama Güncellemesinde",
"DeleteSelectedApplicationsMessageText": "Seçilen {count} içe aktarma listesini silmek istediğinizden emin misiniz?",
"ProxyValidationBadRequest": "Proxy ile test edilemedi. DurumKodu: {statusCode}",
"UpdateAvailableHealthCheckMessage": "Yeni güncelleme mevcut",
"UpdateAvailableHealthCheckMessage": "Yeni güncelleme mevcut: {version}",
"days": "gün",
"Default": "Varsayılan",
"GrabRelease": "Yayın Yakalama",
@@ -534,5 +534,7 @@
"Any": "Herhangi",
"AllSearchResultsHiddenByFilter": "Tüm sonuçlar, uygulanan filtre tarafından gizlenir",
"HealthMessagesInfoBox": "Satırın sonundaki wiki bağlantısını (kitap simgesi) tıklayarak veya [günlüklerinizi]({link}) kontrol ederek bu durum kontrolü mesajlarının nedeni hakkında daha fazla bilgi bulabilirsiniz. Bu mesajları yorumlamakta zorluk yaşıyorsanız aşağıdaki bağlantılardan destek ekibimize ulaşabilirsiniz.",
"PackageVersionInfo": "{packageAuthor} tarafından {packageVersion}"
"PackageVersionInfo": "{packageAuthor} tarafından {packageVersion}",
"LogSizeLimit": "Log Boyutu Sınırı",
"LogSizeLimitHelpText": "Arşivlemeden önce MB cinsinden maksimum log dosya boyutu. Varsayılan 1 MB'tır."
}

View File

@@ -1,6 +1,6 @@
{
"About": "关于",
"AcceptConfirmationModal": "接受确认模组",
"AcceptConfirmationModal": "接受确认对话框",
"Actions": "动作",
"Add": "添加",
"AddApplication": "添加应用程序",
@@ -21,10 +21,10 @@
"AllIndexersHiddenDueToFilter": "由于应用了筛选器,所有索引器都被隐藏。",
"Analytics": "分析",
"AnalyticsEnabledHelpText": "将匿名使用情况和错误信息发送到{appName}的服务器。这包括有关您的浏览器的信息、您使用的{appName} WebUI页面、错误报告以及操作系统和运行时版本。我们将使用此信息来确定功能和错误修复的优先级。",
"ApiKey": "接口密钥 (API Key)",
"ApiKeyValidationHealthCheckMessage": "请将API密钥更新为至少{length}个字符长。您可以通过设置或配置文件执行此操作",
"AppDataDirectory": "AppData目录",
"AppDataLocationHealthCheckMessage": "正在更新期间的 AppData 不会被更新删除",
"ApiKey": "API 密钥",
"ApiKeyValidationHealthCheckMessage": "请将API密钥更新为至少 {length} 个字符长。您可以通过设置或配置文件完成此操作",
"AppDataDirectory": "AppData 目录",
"AppDataLocationHealthCheckMessage": "更新时无法阻止删除 AppData",
"AppProfileInUse": "正在使用的应用程序配置文件",
"AppProfileSelectHelpText": "应用程序配置用于控制应用程序同步设置 RSS、自动搜索和交互式搜索设置",
"AppSettingsSummary": "配置{appName}与PVR程序交互方式的应用和设置",
@@ -34,7 +34,7 @@
"ApplicationStatusCheckAllClientMessage": "由于故障所用应用程序都不可用",
"ApplicationStatusCheckSingleClientMessage": "由于故障应用程序不可用",
"ApplicationURL": "应用程序 URL",
"ApplicationUrlHelpText": "此应用的外部URL包含 http(s)://、端口和基本URL",
"ApplicationUrlHelpText": "此应用的外部 URL包含 http(s)://、端口和基本 URL",
"Applications": "程序",
"Apply": "应用",
"ApplyTags": "应用标签",
@@ -45,9 +45,9 @@
"Auth": "认证",
"Authentication": "认证",
"AuthenticationMethodHelpText": "需要用户名和密码以访问 {appName}",
"AuthenticationRequired": "需要身份验证",
"AuthenticationRequiredHelpText": "修改些请求需要认证。除非你了解其中的风险,否则不要更改。",
"AuthenticationRequiredWarning": "为防止未经身份验证的远程访问,{appName} 现需要启用身份证。您可以禁用本地地址的身份证。",
"AuthenticationRequired": "需要证",
"AuthenticationRequiredHelpText": "修改些请求需要认证。除非你了解其中的风险,否则不要进行更改。",
"AuthenticationRequiredWarning": "为防止未经证的远程访问,{appName} 现需要启用身份证。您可以选择禁用本地地址的身份证。",
"Author": "作者",
"Automatic": "自动化",
"AutomaticSearch": "自动搜索",
@@ -116,7 +116,7 @@
"DevelopmentSettings": "开发设置",
"Disabled": "禁用",
"DisabledUntil": "禁用Until",
"Discord": "分歧",
"Discord": "Discord",
"Docker": "Docker",
"Donations": "赞助",
"DownloadClient": "下载客户端",
@@ -194,7 +194,7 @@
"IndexerDetails": "‎索引器‎‎详细信息‎",
"IndexerDisabled": "索引器已被禁用",
"IndexerFailureRate": "Indexer失败率",
"IndexerFlags": "搜刮器标",
"IndexerFlags": "索引器标",
"IndexerHealthCheckNoIndexers": "未启用任何搜刮器,{appName}将不会返回搜索结果",
"IndexerInfo": "索引器信息",
"IndexerLongTermStatusAllUnavailableHealthCheckMessage": "由于故障超过6小时所有搜刮器均不可用",
@@ -322,7 +322,7 @@
"Queue": "队列",
"Queued": "队列中",
"Rss": "RSS",
"RssIsNotSupportedWithThisIndexer": "该搜刮器不支持RSS",
"RssIsNotSupportedWithThisIndexer": "该索引器不支持 RSS",
"RawSearchSupported": "‎支持原始‎‎搜索‎",
"ReadTheWikiForMoreInformation": "查阅Wiki获得更多信息",
"Reddit": "Reddit",
@@ -368,7 +368,7 @@
"Season": "季",
"Security": "安全",
"Seeders": "种子",
"SelectAll": "选择全部",
"SelectAll": "选",
"SemiPrivate": "‎半私有‎",
"SendAnonymousUsageData": "发送匿名使用数据",
"SetTags": "设置标签",
@@ -389,13 +389,13 @@
"SettingsShowRelativeDatesHelpText": "显示相对日期(今天昨天等)或绝对日期",
"SettingsSqlLoggingHelpText": "记录来自{appName}的所有SQL查询",
"SettingsTimeFormat": "时间格式",
"ShowAdvanced": "显示高级设置",
"ShowAdvanced": "高级设置",
"ShowSearch": "显示搜索",
"ShowSearchHelpText": "悬停时显示搜索按钮",
"Shutdown": "关机",
"Size": "大小",
"Sort": "排序",
"Source": "来源",
"Source": "代码",
"StartTypingOrSelectAPathBelow": "输入路径或者从下面选择",
"Started": "已开始",
"StartupDirectory": "启动目录",
@@ -425,7 +425,7 @@
"TestAll": "测试全部",
"TestAllApps": "测试全部应用",
"TestAllClients": "测试全部客户端",
"TestAllIndexers": "测试全部搜刮器",
"TestAllIndexers": "测试全部索引器",
"TheLatestVersionIsAlreadyInstalled": "已安装最新版本的{appName}",
"Theme": "主题",
"ThemeHelpText": "更改应用程序UI主题“自动”主题将使用您的操作系统主题设置亮或暗模式。灵感来源于{inspirredby}。",
@@ -462,7 +462,7 @@
"UnableToLoadTags": "无法加载标签",
"UnableToLoadUISettings": "无法加载UI设置",
"UnsavedChanges": "未保存更改",
"UnselectAll": "取消选择全部",
"UnselectAll": "取消选",
"UpdateAutomaticallyHelpText": "自动下载并安装更新。你还可以在“系统:更新”中安装",
"UpdateAvailableHealthCheckMessage": "有新的更新可用",
"UpdateStartupNotWritableHealthCheckMessage": "无法安装更新,因为用户“{userName}”对于启动文件夹“{startupFolder}”没有写入权限。",
@@ -484,7 +484,7 @@
"Warn": "警告",
"Website": "‎网站‎",
"Wiki": "Wiki",
"Year": "年",
"Year": "年",
"Yes": "确定",
"YesCancel": "确定,取消",
"Yesterday": "昨天",
@@ -493,9 +493,9 @@
"ApplyChanges": "应用更改",
"ApplyTagsHelpTextAdd": "添加: 添加标签至已有的标签列表中",
"CountDownloadClientsSelected": "已选择 {count} 个下载客户端",
"CountIndexersSelected": "已选择 {count} 个索引器",
"CountIndexersSelected": "选定 {count} 个索引器",
"DeleteSelectedDownloadClientsMessageText": "您确定要删除 {count} 个选定的下载客户端吗?",
"DeleteSelectedIndexersMessageText": "您确定要删除{count}选定的索引器吗?",
"DeleteSelectedIndexersMessageText": "您确定要删除选定的 {count}索引器吗?",
"EditSelectedDownloadClients": "编辑选定的下载客户端",
"Implementation": "执行",
"ManageDownloadClients": "管理下载客户端",
@@ -503,12 +503,12 @@
"NoIndexersFound": "未找到索引器",
"SelectIndexers": "搜刮器搜索",
"ApplyTagsHelpTextHowToApplyApplications": "如何给选中的电影添加标签",
"ApplyTagsHelpTextHowToApplyIndexers": "如何将标签应用到已选的索引器",
"ApplyTagsHelpTextHowToApplyIndexers": "如何将标签应用到已选的索引器",
"ApplyTagsHelpTextReplace": "替换: 用输入的标签替换当前标签 (不输入将会清除所有标签)",
"DeleteSelectedApplicationsMessageText": "您确定要删除{count}选定的应用程序吗?",
"DeleteSelectedDownloadClients": "删除下载客户端",
"DownloadClientPriorityHelpText": "优先考虑多个下载客户端,循环查询用于具有相同优先级的客户端。",
"EditSelectedIndexers": "编辑选定索引器",
"EditSelectedIndexers": "编辑选定索引器",
"OnHealthRestored": "健康度恢复",
"OnHealthRestoredHelpText": "健康度恢复",
"ApplyTagsHelpTextRemove": "移除: 移除已输入的标签",
@@ -559,11 +559,11 @@
"AddConnectionImplementation": "添加连接- {implementationName}",
"AddDownloadClientImplementation": "添加下载客户端- {implementationName}",
"AddIndexerProxyImplementation": "添加搜刮器代理-{实体名称}",
"AppUpdated": "{appName} 升级",
"AppUpdated": "{appName} 升级",
"EditDownloadClientImplementation": "编辑下载客户端- {implementationName}",
"EditIndexerImplementation": "编辑索引器- {implementationName}",
"EditIndexerProxyImplementation": "添加搜刮器代理-{实体名称}",
"AppUpdatedVersion": "{appName} 已经更新到 {version} 版本,重新加载 {appName} 使更新生效",
"AppUpdatedVersion": "{appName} 已经更新到版本 {version} ,重新加载 {appName} 使更新生效",
"EditApplicationImplementation": "添加应用-{实体名称}",
"EditConnectionImplementation": "编辑连接- {implementationName}",
"NotificationStatusAllClientHealthCheckMessage": "由于故障所用应用程序都不可用",
@@ -571,7 +571,7 @@
"AuthBasic": "基础(浏览器弹出对话框)",
"AuthForm": "表单(登陆页面)",
"AuthenticationMethod": "认证方式",
"AuthenticationMethodHelpTextWarning": "请选择一个有效的身份验证方式",
"AuthenticationMethodHelpTextWarning": "请选择一个有效的证方式",
"AuthenticationRequiredPasswordHelpTextWarning": "请输入新密码",
"AuthenticationRequiredUsernameHelpTextWarning": "请输入新用户名",
"Clone": "复制",
@@ -580,7 +580,7 @@
"External": "外部的",
"None": "无",
"ResetAPIKeyMessageText": "您确定要重置您的 API 密钥吗?",
"IndexerDownloadClientHealthCheckMessage": "无效下载客户端的索引器:{indexerNames}。",
"IndexerDownloadClientHealthCheckMessage": "使用无效下载客户端的索引器:{indexerNames}。",
"ApplicationTagsHelpTextWarning": "标签应该谨慎使用,它们可能会产生意想不到的效果。带有标签的应用程序只会与具有相同标签的索引器同步。",
"EditCategory": "编辑分类",
"IndexerHistoryLoadError": "加载索引器历史记录出错",
@@ -603,7 +603,7 @@
"NoIndexerCategories": "没有找到此索引器的分类",
"DownloadClientQbittorrentSettingsContentLayout": "内容布局",
"DownloadClientQbittorrentSettingsContentLayoutHelpText": "是否使用 qBittorrent 配置的内容布局使用种子的原始布局或始终创建子文件夹qBittorrent 4.3.2+",
"DownloadClientAriaSettingsDirectoryHelpText": "可选的下载位置,留空使用 Aria2 默认位置",
"DownloadClientAriaSettingsDirectoryHelpText": "下载位置可选择,留空使用 Aria2 默认位置",
"ManageClients": "管理客户端",
"CustomFilter": "自定义过滤器",
"BlackholeFolderHelpText": "{appName} 将在其中存储 {extension} 文件的文件夹",
@@ -616,7 +616,7 @@
"DownloadClientFloodSettingsAdditionalTags": "附加标签",
"DownloadClientFreeboxSettingsApiUrl": "API 地址",
"DownloadClientFreeboxSettingsAppId": "App ID",
"DownloadClientDelugeSettingsUrlBaseHelpText": "向 deluge json url 添加前缀,请参阅 {url}",
"DownloadClientDelugeSettingsUrlBaseHelpText": "向 Deluge JSON URL 添加前缀,请参阅 {url}",
"DownloadClientFloodSettingsUrlBaseHelpText": "为 Flood API 添加前缀,例如 {url}",
"DownloadClientFreeboxSettingsAppToken": "App Token",
"DownloadClientFreeboxSettingsAppTokenHelpText": "创建访问 Freebox API 所需的 App token即“ app_token”",
@@ -639,7 +639,7 @@
"SecretToken": "密钥令牌",
"XmlRpcPath": "XML RPC 路径",
"ApplicationSettingsSyncRejectBlocklistedTorrentHashesHelpText": "如果 torrent 的哈希被屏蔽了某些索引器在使用RSS或者搜索期间可能无法正确拒绝它启用此功能将允许在抓取 torrent 之后但在将其发送到客户端之前拒绝它。",
"DownloadClientDownloadStationSettingsDirectoryHelpText": "用于存放下载内容的可选共享文件夹,留空使用默认的 Download Station 位置",
"DownloadClientDownloadStationSettingsDirectoryHelpText": "用于存放下载内容的共享文件夹可选择,留空使用默认的 Download Station 位置",
"DownloadClientFloodSettingsAdditionalTagsHelpText": "添加媒体属性作为标签。 提示是示例。",
"DownloadClientFreeboxSettingsApiUrlHelpText": "使用 API 版本定义 Freebox API 基本 URL例如“{url}”,默认为“{defaultApiUrl}”",
"DownloadClientPneumaticSettingsStrmFolder": "Strm 文件夹",
@@ -680,10 +680,10 @@
"IndexerSettingsCookie": "Cookie",
"ProxyValidationBadRequest": "测试代理失败。状态码:{statusCode}",
"ProxyValidationUnableToConnect": "无法连接到索引器:{exceptionMessage}。 检查有关此错误的日志以了解详细信息",
"DownloadClientFloodSettingsTagsHelpText": "下载的初始标签。 要被识别,下载必须具有所有初始标签。 这可以避免与不相关的下载发生冲突。",
"DownloadClientFloodSettingsTagsHelpText": "下载的初始标签。下载必须具有所有初始标签才可被识别。 这可以避免与不相关的下载发生冲突。",
"DownloadClientPneumaticSettingsStrmFolderHelpText": "该文件夹中的 .strm 文件将由 drone 导入",
"IndexerHDBitsSettingsCodecsHelpText": "如果未指定,则使用所有选项。",
"IndexerSettingsSeedRatioHelpText": "种子在停止之前应达到的比率,留空使用下载客户端的默认值。 比率应至少为 1.0 并遵循索引器规则",
"IndexerSettingsSeedRatioHelpText": "停止之前应达到的做种比率,留空使用下载客户端的默认值。 比率应至少为 1.0 并遵循索引器规则",
"IndexerSettingsSeedTimeHelpText": "停止前应做种的时间,留空使用下载客户端的默认值",
"TorrentBlackholeSaveMagnetFiles": "保存磁力链接文件",
"TorrentBlackholeTorrentFolder": "种子文件夹",
@@ -697,5 +697,19 @@
"Redirected": "重定向",
"AllSearchResultsHiddenByFilter": "根据过滤条件所有结果已隐藏",
"HealthMessagesInfoBox": "您可以通过单击行尾的wiki链接(图书图标)或检查[日志]({link})来查找有关这些运行状况检查消息原因的更多信息。如果你在理解这些信息方面有困难,你可以通过下面的链接联系我们的支持。",
"PackageVersionInfo": "{packageVersion} 由 {packageAuthor} 制作"
"PackageVersionInfo": "{packageVersion} 由 {packageAuthor} 制作",
"LabelIsRequired": "需要标签",
"LogSizeLimit": "日志大小限制",
"LogSizeLimitHelpText": "存档前的最大日志文件大小MB。默认值为 1 MB。",
"NotificationsTelegramSettingsIncludeAppName": "标题中包含 {appName}",
"NotificationsTelegramSettingsIncludeAppNameHelpText": "可选,在消息标题前加上 {appName} 以区分来自不同应用的通知",
"NotificationsEmailSettingsUseEncryption": "启用加密",
"NotificationsEmailSettingsUseEncryptionHelpText": "是否优先使用加密如果服务器已配置始终使用通过SSL仅端口465或StartTLS任何其他端口进行加密或从不使用加密",
"ClickToChangeQueryOptions": "单击以更改查询选项",
"ApplicationsLoadError": "无法加载应用程序列表",
"DownloadClientSettingsDefaultCategoryHelpText": "默认的备用分类,当发布资源没有匹配的分类时将使用此分类。为 {appName} 添加一个特定的分类,可以避免与非 {appName} 的无关下载发生冲突。分类是可选的,但强烈建议使用。",
"AverageGrabs": "平均抓取次数",
"AverageQueries": "平均查询次数",
"DefaultCategory": "默认分类",
"DownloadClientSettingsDefaultCategorySubFolderHelpText": "默认的备用分类,当发布资源没有匹配的分类时将使用此分类。为 {appName} 添加一个特定的分类,可以避免与非 {appName} 的无关下载发生冲突。分类是可选的,但强烈建议使用。启用分类后,会在输出目录中创建一个 [分类] 子目录。"
}

View File

@@ -5,12 +5,13 @@
<ItemGroup>
<PackageReference Include="AngleSharp.Xml" Version="1.0.0" />
<PackageReference Include="Dapper" Version="2.0.151" />
<PackageReference Include="Diacritical.Net" Version="1.0.4" />
<PackageReference Include="MailKit" Version="3.6.0" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="6.0.32" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="6.0.35" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="Npgsql" Version="7.0.7" />
<PackageReference Include="Polly" Version="8.4.1" />
<PackageReference Include="Npgsql" Version="7.0.8" />
<PackageReference Include="Polly" Version="8.4.2" />
<PackageReference Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
<PackageReference Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
<PackageReference Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
@@ -20,7 +21,7 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.3.3" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Text.Json" Version="6.0.9" />
<PackageReference Include="System.Text.Json" Version="6.0.10" />
<PackageReference Include="MonoTorrent" Version="2.0.7" />
<PackageReference Include="YamlDotNet" Version="13.1.1" />
<PackageReference Include="AngleSharp" Version="1.1.2" />

View File

@@ -135,7 +135,7 @@ namespace NzbDrone.Host
Name = "apikey",
Type = SecuritySchemeType.ApiKey,
Scheme = "apiKey",
Description = "Apikey passed as header",
Description = "Apikey passed as query parameter",
In = ParameterLocation.Query,
Reference = new OpenApiReference
{

View File

@@ -4,7 +4,7 @@
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.32" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.35" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Prowlarr.Test.Common.csproj" />

View File

@@ -25,7 +25,13 @@ namespace Prowlarr.Api.V1.History
public PagingResource<HistoryResource> GetHistory([FromQuery] PagingRequestResource paging, [FromQuery(Name = "eventType")] int[] eventTypes, bool? successful, string downloadId, [FromQuery] int[] indexerIds = null)
{
var pagingResource = new PagingResource<HistoryResource>(paging);
var pagingSpec = pagingResource.MapToPagingSpec<HistoryResource, NzbDrone.Core.History.History>("date", SortDirection.Descending);
var pagingSpec = pagingResource.MapToPagingSpec<HistoryResource, NzbDrone.Core.History.History>(
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"date"
},
"date",
SortDirection.Descending);
if (eventTypes != null && eventTypes.Any())
{

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? PreferMagnetUrl { 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.PreferMagnetUrl = resource.PreferMagnetUrl ?? ((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.PreferMagnetUrl;
}
});

View File

@@ -119,20 +119,29 @@ namespace Prowlarr.Api.V1.Indexers
var settings = (CardigannSettings)definition.Settings;
var cardigannDefinition = _definitionService.GetCachedDefinition(settings.DefinitionFile);
foreach (var field in resource.Fields)
if (settings.DefinitionFile.IsNotNullOrWhiteSpace())
{
if (!standardFields.Contains(field.Name))
var cardigannDefinition = _definitionService.GetCachedDefinition(settings.DefinitionFile);
foreach (var field in resource.Fields)
{
if (field.Name == "cardigannCaptcha")
if (!standardFields.Contains(field.Name))
{
settings.ExtraFieldData["CAPTCHA"] = field.Value?.ToString() ?? string.Empty;
}
else
{
var cardigannSetting = cardigannDefinition.Settings.FirstOrDefault(x => x.Name == field.Name);
settings.ExtraFieldData[field.Name] = MapValue(cardigannSetting, field.Value);
if (field.Name == "cardigannCaptcha")
{
settings.ExtraFieldData["CAPTCHA"] = field.Value?.ToString() ?? string.Empty;
}
else
{
var cardigannSetting = cardigannDefinition.Settings.FirstOrDefault(x => x.Name == field.Name);
if (cardigannSetting == null)
{
throw new ArgumentOutOfRangeException(field.Name, "Unknown Cardigann setting.");
}
settings.ExtraFieldData[field.Name] = MapValue(cardigannSetting, field.Value);
}
}
}
}

View File

@@ -198,7 +198,9 @@ namespace NzbDrone.Api.V1.Indexers
}
}
return CreateResponse(results.ToXml(indexer.Protocol));
var preferMagnetUrl = indexer.Protocol == DownloadProtocol.Torrent && indexerDef.Settings is ITorrentIndexerSettings torrentIndexerSettings && (torrentIndexerSettings.TorrentBaseSettings?.PreferMagnetUrl ?? false);
return CreateResponse(results.ToXml(indexer.Protocol, preferMagnetUrl));
default:
return CreateResponse(CreateErrorXML(202, $"No such function ({requestType})"), statusCode: StatusCodes.Status400BadRequest);
}
@@ -253,20 +255,25 @@ namespace NzbDrone.Api.V1.Indexers
var source = Request.GetSource();
var host = Request.GetHostName();
var unprotectedlLink = _downloadMappingService.ConvertToNormalLink(link);
var unprotectedLink = _downloadMappingService.ConvertToNormalLink(link);
if (unprotectedLink.IsNullOrWhiteSpace())
{
throw new BadRequestException("Failed to normalize provided link");
}
// If Indexer is set to download via Redirect then just redirect to the link
if (indexer.SupportsRedirect && indexerDef.Redirect)
{
_downloadService.RecordRedirect(unprotectedlLink, id, source, host, file);
return RedirectPermanent(unprotectedlLink);
_downloadService.RecordRedirect(unprotectedLink, id, source, host, file);
return RedirectPermanent(unprotectedLink);
}
byte[] downloadBytes;
try
{
downloadBytes = await _downloadService.DownloadReport(unprotectedlLink, id, source, host, file);
downloadBytes = await _downloadService.DownloadReport(unprotectedLink, id, source, host, file);
}
catch (ReleaseUnavailableException ex)
{

View File

@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
@@ -29,7 +31,11 @@ namespace Prowlarr.Api.V1.Logs
}
var pagingResource = new PagingResource<LogResource>(paging);
var pageSpec = pagingResource.MapToPagingSpec<LogResource, Log>();
var pageSpec = pagingResource.MapToPagingSpec<LogResource, Log>(new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"id",
"time"
});
if (pageSpec.SortKey == "time")
{

View File

@@ -5089,6 +5089,10 @@
"type": "integer",
"format": "int32",
"nullable": true
},
"preferMagnetUrl": {
"type": "boolean",
"nullable": true
}
},
"additionalProperties": false
@@ -6373,7 +6377,7 @@
},
"apikey": {
"type": "apiKey",
"description": "Apikey passed as header",
"description": "Apikey passed as query parameter",
"name": "apikey",
"in": "query"
}

View File

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

View File

@@ -1,12 +1,18 @@
using System;
using System.Text.RegularExpressions;
using Diacritical;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.DependencyInjection;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
namespace Prowlarr.Http.Authentication
{
public static class AuthenticationBuilderExtensions
{
private static readonly Regex CookieNameRegex = new Regex(@"[^a-z0-9]+", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static AuthenticationBuilder AddApiKey(this AuthenticationBuilder authenticationBuilder, string name, Action<ApiKeyAuthenticationOptions> options)
{
return authenticationBuilder.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(name, options);
@@ -29,19 +35,27 @@ namespace Prowlarr.Http.Authentication
public static AuthenticationBuilder AddAppAuthentication(this IServiceCollection services)
{
return services.AddAuthentication()
.AddNone(AuthenticationType.None.ToString())
.AddExternal(AuthenticationType.External.ToString())
.AddBasic(AuthenticationType.Basic.ToString())
.AddCookie(AuthenticationType.Forms.ToString(), options =>
services.AddOptions<CookieAuthenticationOptions>(AuthenticationType.Forms.ToString())
.Configure<IConfigFileProvider>((options, configFileProvider) =>
{
options.Cookie.Name = "ProwlarrAuth";
// Replace diacritics and replace non-word characters to ensure cookie name doesn't contain any valid URL characters not allowed in cookie names
var instanceName = configFileProvider.InstanceName;
instanceName = instanceName.RemoveDiacritics();
instanceName = CookieNameRegex.Replace(instanceName, string.Empty);
options.Cookie.Name = $"{instanceName}Auth";
options.AccessDeniedPath = "/login?loginFailed=true";
options.LoginPath = "/login";
options.ExpireTimeSpan = TimeSpan.FromDays(7);
options.SlidingExpiration = true;
options.ReturnUrlParameter = "returnUrl";
})
});
return services.AddAuthentication()
.AddNone(AuthenticationType.None.ToString())
.AddExternal(AuthenticationType.External.ToString())
.AddBasic(AuthenticationType.Basic.ToString())
.AddCookie(AuthenticationType.Forms.ToString())
.AddApiKey("API", options =>
{
options.HeaderName = "X-Api-Key";

View File

@@ -20,6 +20,8 @@ namespace Prowlarr.Http.Middleware
if (_urlBase.IsNotNullOrWhiteSpace() && context.Request.PathBase.Value.IsNullOrWhiteSpace())
{
context.Response.Redirect($"{_urlBase}{context.Request.Path}{context.Request.QueryString}");
context.Response.StatusCode = 307;
return;
}

View File

@@ -38,7 +38,11 @@ namespace Prowlarr.Http
public static class PagingResourceMapper
{
public static PagingSpec<TModel> MapToPagingSpec<TResource, TModel>(this PagingResource<TResource> pagingResource, string defaultSortKey = "Id", SortDirection defaultSortDirection = SortDirection.Ascending)
public static PagingSpec<TModel> MapToPagingSpec<TResource, TModel>(
this PagingResource<TResource> pagingResource,
HashSet<string> allowedSortKeys,
string defaultSortKey = "id",
SortDirection defaultSortDirection = SortDirection.Ascending)
{
var pagingSpec = new PagingSpec<TModel>
{
@@ -48,14 +52,15 @@ namespace Prowlarr.Http
SortDirection = pagingResource.SortDirection,
};
if (pagingResource.SortKey == null)
{
pagingSpec.SortKey = defaultSortKey;
if (pagingResource.SortDirection == SortDirection.Default)
{
pagingSpec.SortDirection = defaultSortDirection;
}
}
pagingSpec.SortKey = pagingResource.SortKey != null &&
allowedSortKeys is { Count: > 0 } &&
allowedSortKeys.Contains(pagingResource.SortKey)
? pagingResource.SortKey
: defaultSortKey;
pagingSpec.SortDirection = pagingResource.SortDirection == SortDirection.Default
? defaultSortDirection
: pagingResource.SortDirection;
return pagingSpec;
}

3240
yarn.lock

File diff suppressed because it is too large Load Diff