Compare commits

..

61 Commits

Author SHA1 Message Date
Weblate
00bd9c241a Multiple Translations updated by Weblate
ignore-downstream

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

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

Co-authored-by: David13467 <davidnow00@gmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Steve Hansen <steve@hansenconsultancy.be>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2024-02-16 01:52:35 +02:00
Bogdan
a1a5dd574e Fixed: Using FlareSolverr in the same time with another HTTP/SOCKS proxy 2024-02-16 01:50:56 +02:00
Bogdan
a5ecc2dc9f Show download client ID as hint in select options 2024-02-14 18:36:57 +02:00
Bogdan
7d46660583 Fixed: Updated base url for TorrentsCSV 2024-02-14 01:41:14 +02:00
Bogdan
22cbf40e3c Bump label-actions action to v4 2024-02-13 15:49:26 +02:00
Weblate
25821c758f Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2024-02-12 21:20:01 +02:00
Bogdan
6153737a78 Translations for download client settings
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
2024-02-12 21:17:54 +02:00
Bogdan
07adb45d63 Bump version to 1.14.0 2024-02-12 02:22:39 +02:00
Bogdan
02bc40b9b6 Translations for indexer settings 2024-02-10 21:29:01 +02:00
Weblate
83e7e30e4f Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Aitzol Garmendia <aitzolgarmendia@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: aghus <aghus.m@outlook.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nb_NO/
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/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translation: Servarr/Prowlarr
2024-02-10 19:49:52 +02:00
bakerboy448
ae870fd46a Improve HDBits Settings Helptext 2024-02-09 21:31:15 +02:00
Weblate
33b7ba8725 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Hicabi Erdem <bilgi@hicabierdem.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: aghus <aghus.m@outlook.com>
Co-authored-by: savin-msk <ns@a77.io>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
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/uk/
Translation: Servarr/Prowlarr
2024-02-09 15:34:37 +02:00
Bogdan
dd2567a85f Fixed: Refresh tags state to clear removed tags by housekeeping 2024-02-07 23:24:25 +02:00
Bogdan
264ff8f885 Revert "Enable browser navigation buttons for PWA"
This reverts commit 1b36951879.
2024-02-07 18:29:27 +02:00
Mark McDowall
629c6a8891 Fixed: Redirecting after login with urlbase
(cherry picked from commit 745b92daf4bf4b9562ffe52dad84a12a5561add5)
2024-02-07 09:02:37 +02:00
Mark McDowall
0ce2f96789 New: Log database engine version on startup
(cherry picked from commit 6ab1d8e16b29e98b4d2ebb68e0356f6f2d3a2c10)
2024-02-07 09:02:19 +02:00
Mark McDowall
cd7d1571db Fixed: Don't use sub folder to check for free disk space for update
(cherry picked from commit f722d49b3a9efefa65bef1b24d90be9332ca62ea)
2024-02-07 09:01:59 +02:00
abcasada
4558f55282 Hints for week column and short dates in UI settings 2024-02-06 22:07:41 +02:00
Bogdan
21589fda57 Bump version to 1.13.3 2024-02-04 12:53:06 +02:00
Weblate
3496263cd2 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Alexander <a.burdun@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Crocmou <slaanesh8854@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Ole Nørby <ole@olenoerby.dk>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Stas Panasiuk <temnyip@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/ca/
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/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_TW/
Translation: Servarr/Prowlarr
2024-02-03 22:46:02 +02:00
Bogdan
1bb1ec6106 Fixed: (IPTorrents) Include episodes in season search 2024-02-03 00:13:50 +02:00
Bogdan
2bfb838933 Bind shortcut for pending changes confirmation only when it's shown 2024-01-31 19:46:55 +02:00
Bogdan
9eb291f578 Fixed: Avoid trailing slash in Proxy Url sent to FlareSolverr 2024-01-31 15:26:26 +02:00
Servarr
8cf892124c Automated API Docs update 2024-01-28 10:08:24 +02:00
Mark McDowall
47fb886930 Don't clone indexer API Key
(cherry picked from commit d336aaf3f04136471970155b5a7cc876770c64ff)
2024-01-28 09:59:49 +02:00
Mark McDowall
5034a211cb New: Don't return API Keys and Passwords via the API
(cherry picked from commit 570be882154e73f8ad1de5b16b957bcb964697fd)

Don't replace private values that haven't been set

(cherry picked from commit 52760e0908fa9852ed8a770f1916bb582eb8c8b4)
2024-01-28 09:59:49 +02:00
Bogdan
ed1364b6ff Update magnet trackers 2024-01-28 09:08:55 +02:00
Bogdan
71e18b616d Bump version to 1.13.2 2024-01-28 09:07:13 +02:00
abcasada
f7bf21df68 Update help text for Tags usage in Applications 2024-01-26 19:13:12 +02:00
Bogdan
d764e3405d New: (Torznab) Internal and Scene tags 2024-01-26 09:58:22 +02:00
Bogdan
16baceb784 New: Option to disable Email encryption
* New: Option to disable Email encryption

(cherry picked from commit 7be5732a3a6679120b0f01bd1eb1207194f57f5e)

* Fix possible NullRef in Email Encryption migration

(cherry picked from commit 271266b10ac51ee6dd7a7024d346b631bd5397c2)
2024-01-24 11:37:01 +02:00
Bogdan
5d2b80d15a Fixed: (SubsPlease) Parse release size from magnet links 2024-01-23 10:59:59 +02:00
Bogdan
a20a81f424 New: Category filter for Indexers 2024-01-23 09:00:49 +02:00
Bogdan
ebb66e9086 Update database migration version translation token 2024-01-23 08:50:05 +02:00
Bogdan
cb8797693e Fixed: Sorting by name in Manage Applications and Download Client modals 2024-01-23 08:40:32 +02:00
Stevie Robinson
255c6335ae New: Add sorting to Manage Applications and Download Client modals
(cherry picked from commit 91053ca51ded804739f94ee936c1376a755dbe11)
2024-01-23 08:38:47 +02:00
Bogdan
155cd53dcd Fix translations for manage applications and download clients 2024-01-23 08:29:59 +02:00
Weblate
ae70a96c10 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Julian Baquero <julian-baquero@upc.edu.co>
Co-authored-by: MaddionMax <kovacs.tamas@ius.hu>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: blabla <romcrack56@gmail.com>
Co-authored-by: brn <barantsenkul@gmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: horvi28 <horvi28@gmail.com>
Co-authored-by: wilfriedarma <wilfriedarma.collet@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
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/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translation: Servarr/Prowlarr
2024-01-23 08:15:51 +02:00
Bogdan
16c0daf090 Fix value types for EnhancedSelectInputConnector 2024-01-22 13:24:00 +02:00
Bogdan
34c78c5a9d Fixed: (BTN) Disable ID-based searches for episodes with absolute episode number
Release name searches are not supported, so it's better to prevent an useless request.
2024-01-22 11:48:32 +02:00
Bogdan
dd5b108ffd Fixed: (BTN) Allow ID-based with text search query 2024-01-21 17:13:12 +02:00
Servarr
0b83986255 Automated API Docs update 2024-01-21 07:54:16 +02:00
Bogdan
2bd25fb6f3 Reorder sync fields for App Profiles 2024-01-21 07:47:48 +02:00
Bogdan
0f5eb5d3a3 Bump version to 1.13.1 2024-01-21 07:47:48 +02:00
Bogdan
c9434c61e3 Transpile logical assignment operators with babel 2024-01-21 03:58:55 +02:00
Mark McDowall
ee969b7a06 New: Log warning if less than 1 GB free space during update
(cherry picked from commit e66ba84fc0b5b120dd4e87f6b8ae1b3c038ee72b)
2024-01-21 03:58:28 +02:00
150 changed files with 2028 additions and 2538 deletions

View File

@@ -18,6 +18,6 @@ jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/label-actions@v3
- uses: dessant/label-actions@v4
with:
process-only: 'issues, prs'

View File

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

View File

@@ -2,6 +2,8 @@ const loose = true;
module.exports = {
plugins: [
'@babel/plugin-transform-logical-assignment-operators',
// Stage 1
'@babel/plugin-proposal-export-default-from',
['@babel/plugin-transform-optional-chaining', { loose }],

View File

@@ -3,6 +3,7 @@ import AppSectionState, {
AppSectionItemState,
AppSectionSaveState,
} from 'App/State/AppSectionState';
import { IndexerCategory } from 'Indexer/Indexer';
import Application from 'typings/Application';
import DownloadClient from 'typings/DownloadClient';
import Notification from 'typings/Notification';
@@ -25,6 +26,11 @@ export interface DownloadClientAppState
AppSectionDeleteState,
AppSectionSaveState {}
export interface IndexerCategoryAppState
extends AppSectionState<IndexerCategory>,
AppSectionDeleteState,
AppSectionSaveState {}
export interface NotificationAppState
extends AppSectionState<Notification>,
AppSectionDeleteState {}
@@ -35,6 +41,7 @@ interface SettingsAppState {
appProfiles: AppProfileAppState;
applications: ApplicationAppState;
downloadClients: DownloadClientAppState;
indexerCategories: IndexerCategoryAppState;
notifications: NotificationAppState;
ui: UiSettingsAppState;
}

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import { IndexerCategory } from 'Indexer/Indexer';
import FilterBuilderRowValue from './FilterBuilderRowValue';
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
const indexerCategoriesSelector = createSelector(
(state: AppState) => state.settings.indexerCategories,
(categories) => categories.items
);
function CategoryFilterBuilderRowValue(props: FilterBuilderRowValueProps) {
const categories: IndexerCategory[] = useSelector(indexerCategoriesSelector);
const tagList = categories.reduce(
(acc: { id: number; name: string }[], element) => {
acc.push({
id: element.id,
name: `${element.name} (${element.id})`,
});
if (element.subCategories && element.subCategories.length > 0) {
element.subCategories.forEach((subCat) => {
acc.push({
id: subCat.id,
name: `${subCat.name} (${subCat.id})`,
});
});
}
return acc;
},
[]
);
return <FilterBuilderRowValue {...props} tagList={tagList} />;
}
export default CategoryFilterBuilderRowValue;

View File

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

View File

@@ -5,6 +5,7 @@ import IconButton from 'Components/Link/IconButton';
import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Props';
import AppProfileFilterBuilderRowValueConnector from './AppProfileFilterBuilderRowValueConnector';
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
import CategoryFilterBuilderRowValue from './CategoryFilterBuilderRowValue';
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
import HistoryEventTypeFilterBuilderRowValue from './HistoryEventTypeFilterBuilderRowValue';
@@ -56,6 +57,9 @@ function getRowValueConnector(selectedFilterBuilderProp) {
case filterBuilderValueTypes.BOOL:
return BoolFilterBuilderRowValue;
case filterBuilderValueTypes.CATEGORY:
return CategoryFilterBuilderRowValue;
case filterBuilderValueTypes.DATE:
return DateFilterBuilderRowValue;

View File

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

View File

@@ -24,7 +24,8 @@ function createMapStateToProps() {
.sort(sortByName)
.map((downloadClient) => ({
key: downloadClient.id,
value: downloadClient.name
value: downloadClient.name,
hint: `(${downloadClient.id})`
}));
if (includeAny) {

View File

@@ -147,7 +147,7 @@ EnhancedSelectInputConnector.propTypes = {
provider: PropTypes.string.isRequired,
providerData: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])).isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number)]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
selectOptionsProviderAction: PropTypes.string,
onChange: PropTypes.func.isRequired,

View File

@@ -15,5 +15,5 @@
"start_url": "../../../../",
"theme_color": "#3a3f51",
"background_color": "#3a3f51",
"display": "minimal-ui"
"display": "standalone"
}

View File

@@ -7,5 +7,5 @@ export const INDEXER = 'indexer';
export const PROTOCOL = 'protocol';
export const PRIVACY = 'privacy';
export const APP_PROFILE = 'appProfile';
export const MOVIE_STATUS = 'movieStatus';
export const CATEGORY = 'category';
export const TAG = 'tag';

View File

@@ -97,7 +97,7 @@ function EditIndexerModalContent(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="enable"
helpTextWarning={supportsRss.value ? undefined : translate('RSSIsNotSupportedWithThisIndexer')}
helpTextWarning={supportsRss.value ? undefined : translate('RssIsNotSupportedWithThisIndexer')}
{...enable}
onChange={onInputChange}
/>
@@ -144,6 +144,7 @@ function EditIndexerModalContent(props) {
}) :
null
}
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}

View File

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

View File

@@ -62,7 +62,7 @@ class Applications extends Component {
return (
<FieldSet legend={translate('Applications')}>
<PageSectionContent
errorMessage={translate('UnableToLoadApplicationList')}
errorMessage={translate('ApplicationsLoadError')}
{...otherProps}
>
<div className={styles.applications}>

View File

@@ -14,9 +14,11 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import useSelectState from 'Helpers/Hooks/useSelectState';
import { kinds } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import {
bulkDeleteApplications,
bulkEditApplications,
setManageApplicationsSort,
} from 'Store/Actions/settingsActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { SelectStateInputProps } from 'typings/props';
@@ -62,6 +64,8 @@ const COLUMNS = [
interface ManageApplicationsModalContentProps {
onModalClose(): void;
sortKey?: string;
sortDirection?: SortDirection;
}
function ManageApplicationsModalContent(
@@ -76,6 +80,8 @@ function ManageApplicationsModalContent(
isSaving,
error,
items,
sortKey,
sortDirection,
}: ApplicationAppState = useSelector(
createClientSideCollectionSelector('settings.applications')
);
@@ -96,6 +102,13 @@ function ManageApplicationsModalContent(
const selectedCount = selectedIds.length;
const onSortPress = useCallback(
(value: string) => {
dispatch(setManageApplicationsSort({ sortKey: value }));
},
[dispatch]
);
const onDeletePress = useCallback(() => {
setIsDeleteModalOpen(true);
}, [setIsDeleteModalOpen]);
@@ -201,6 +214,9 @@ function ManageApplicationsModalContent(
allSelected={allSelected}
allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange}
sortKey={sortKey}
sortDirection={sortDirection}
onSortPress={onSortPress}
>
<TableBody>
{items.map((item) => {

View File

@@ -84,7 +84,7 @@ class DownloadClientSettings extends Component {
/>
<PageToolbarButton
label={translate('ManageDownloadClients')}
label={translate('ManageClients')}
iconName={icons.MANAGE}
onPress={this.onManageDownloadClientsPress}
/>

View File

@@ -61,7 +61,7 @@ class DownloadClients extends Component {
return (
<FieldSet legend={translate('DownloadClients')}>
<PageSectionContent
errorMessage={translate('UnableToLoadDownloadClients')}
errorMessage={translate('DownloadClientsLoadError')}
{...otherProps}
>
<div className={styles.downloadClients}>

View File

@@ -14,9 +14,11 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import useSelectState from 'Helpers/Hooks/useSelectState';
import { kinds } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import {
bulkDeleteDownloadClients,
bulkEditDownloadClients,
setManageDownloadClientsSort,
} from 'Store/Actions/settingsActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { SelectStateInputProps } from 'typings/props';
@@ -61,6 +63,8 @@ const COLUMNS = [
interface ManageDownloadClientsModalContentProps {
onModalClose(): void;
sortKey?: string;
sortDirection?: SortDirection;
}
function ManageDownloadClientsModalContent(
@@ -75,6 +79,8 @@ function ManageDownloadClientsModalContent(
isSaving,
error,
items,
sortKey,
sortDirection,
}: DownloadClientAppState = useSelector(
createClientSideCollectionSelector('settings.downloadClients')
);
@@ -93,6 +99,13 @@ function ManageDownloadClientsModalContent(
const selectedCount = selectedIds.length;
const onSortPress = useCallback(
(value: string) => {
dispatch(setManageDownloadClientsSort({ sortKey: value }));
},
[dispatch]
);
const onDeletePress = useCallback(() => {
setIsDeleteModalOpen(true);
}, [setIsDeleteModalOpen]);
@@ -174,6 +187,9 @@ function ManageDownloadClientsModalContent(
allSelected={allSelected}
allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange}
sortKey={sortKey}
sortDirection={sortDirection}
onSortPress={onSortPress}
>
<TableBody>
{items.map((item) => {

View File

@@ -15,12 +15,17 @@ function PendingChangesModal(props) {
isOpen,
onConfirm,
onCancel,
bindShortcut
bindShortcut,
unbindShortcut
} = props;
useEffect(() => {
bindShortcut('enter', onConfirm);
}, [bindShortcut, onConfirm]);
if (isOpen) {
bindShortcut('enter', onConfirm);
return () => unbindShortcut('enter', onConfirm);
}
}, [bindShortcut, unbindShortcut, isOpen, onConfirm]);
return (
<Modal
@@ -61,7 +66,8 @@ PendingChangesModal.propTypes = {
kind: PropTypes.oneOf(kinds.all),
onConfirm: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
bindShortcut: PropTypes.func.isRequired
bindShortcut: PropTypes.func.isRequired,
unbindShortcut: PropTypes.func.isRequired
};
PendingChangesModal.defaultProps = {

View File

@@ -96,7 +96,7 @@ class AppProfile extends Component {
kind={enableRss ? kinds.SUCCESS : kinds.DISABLED}
outline={!enableRss}
>
{translate('RSS')}
{translate('Rss')}
</Label>
}

View File

@@ -97,20 +97,6 @@ class EditAppProfileModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('EnableInteractiveSearch')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableInteractiveSearch"
{...enableInteractiveSearch}
helpText={translate('EnableInteractiveSearchHelpText')}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('EnableAutomaticSearch')}
@@ -125,6 +111,20 @@ class EditAppProfileModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('EnableInteractiveSearch')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableInteractiveSearch"
{...enableInteractiveSearch}
helpText={translate('EnableInteractiveSearchHelpText')}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('MinimumSeeders')}

View File

@@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchApplications, fetchIndexerProxies, fetchNotifications } from 'Store/Actions/settingsActions';
import { fetchTagDetails } from 'Store/Actions/tagActions';
import { fetchTagDetails, fetchTags } from 'Store/Actions/tagActions';
import Tags from './Tags';
function createMapStateToProps() {
@@ -25,6 +25,7 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
dispatchFetchTags: fetchTags,
dispatchFetchTagDetails: fetchTagDetails,
dispatchFetchNotifications: fetchNotifications,
dispatchFetchIndexerProxies: fetchIndexerProxies,
@@ -38,12 +39,14 @@ class MetadatasConnector extends Component {
componentDidMount() {
const {
dispatchFetchTags,
dispatchFetchTagDetails,
dispatchFetchNotifications,
dispatchFetchIndexerProxies,
dispatchFetchApplications
} = this.props;
dispatchFetchTags();
dispatchFetchTagDetails();
dispatchFetchNotifications();
dispatchFetchIndexerProxies();
@@ -63,6 +66,7 @@ class MetadatasConnector extends Component {
}
MetadatasConnector.propTypes = {
dispatchFetchTags: PropTypes.func.isRequired,
dispatchFetchTagDetails: PropTypes.func.isRequired,
dispatchFetchNotifications: PropTypes.func.isRequired,
dispatchFetchIndexerProxies: PropTypes.func.isRequired,

View File

@@ -21,19 +21,19 @@ export const firstDayOfWeekOptions = [
];
export const weekColumnOptions = [
{ key: 'ddd M/D', value: 'Tue 3/25' },
{ key: 'ddd MM/DD', value: 'Tue 03/25' },
{ key: 'ddd D/M', value: 'Tue 25/3' },
{ key: 'ddd DD/MM', value: 'Tue 25/03' }
{ key: 'ddd M/D', value: 'Tue 3/25', hint: 'ddd M/D' },
{ key: 'ddd MM/DD', value: 'Tue 03/25', hint: 'ddd MM/DD' },
{ key: 'ddd D/M', value: 'Tue 25/3', hint: 'ddd D/M' },
{ key: 'ddd DD/MM', value: 'Tue 25/03', hint: 'ddd DD/MM' }
];
const shortDateFormatOptions = [
{ key: 'MMM D YYYY', value: 'Mar 25 2014' },
{ key: 'DD MMM YYYY', value: '25 Mar 2014' },
{ key: 'MM/D/YYYY', value: '03/25/2014' },
{ key: 'MM/DD/YYYY', value: '03/25/2014' },
{ key: 'DD/MM/YYYY', value: '25/03/2014' },
{ key: 'YYYY-MM-DD', value: '2014-03-25' }
{ key: 'MMM D YYYY', value: 'Mar 25 2014', hint: 'MMM D YYYY' },
{ key: 'DD MMM YYYY', value: '25 Mar 2014', hint: 'DD MMM YYYY' },
{ key: 'MM/D/YYYY', value: '03/25/2014', hint: 'MM/D/YYYY' },
{ key: 'MM/DD/YYYY', value: '03/25/2014', hint: 'MM/DD/YYYY' },
{ key: 'DD/MM/YYYY', value: '25/03/2014', hint: 'DD/MM/YYYY' },
{ key: 'YYYY-MM-DD', value: '2014-03-25', hint: 'YYYY-MM-DD' }
];
const longDateFormatOptions = [

View File

@@ -1,4 +1,5 @@
import { createAction } from 'redux-actions';
import { sortDirections } from 'Helpers/Props';
import createBulkEditItemHandler from 'Store/Actions/Creators/createBulkEditItemHandler';
import createBulkRemoveItemHandler from 'Store/Actions/Creators/createBulkRemoveItemHandler';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
@@ -7,6 +8,7 @@ import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHand
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler';
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
import createSetClientSideCollectionSortReducer from 'Store/Actions/Creators/Reducers/createSetClientSideCollectionSortReducer';
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import { createThunk } from 'Store/thunks';
@@ -30,9 +32,10 @@ export const CANCEL_SAVE_APPLICATION = 'settings/applications/cancelSaveApplicat
export const DELETE_APPLICATION = 'settings/applications/deleteApplication';
export const TEST_APPLICATION = 'settings/applications/testApplication';
export const CANCEL_TEST_APPLICATION = 'settings/applications/cancelTestApplication';
export const TEST_ALL_APPLICATIONS = 'indexers/testAllApplications';
export const TEST_ALL_APPLICATIONS = 'settings/applications/testAllApplications';
export const BULK_EDIT_APPLICATIONS = 'settings/applications/bulkEditApplications';
export const BULK_DELETE_APPLICATIONS = 'settings/applications/bulkDeleteApplications';
export const SET_MANAGE_APPLICATIONS_SORT = 'settings/applications/setManageApplicationsSort';
//
// Action Creators
@@ -49,6 +52,7 @@ export const cancelTestApplication = createThunk(CANCEL_TEST_APPLICATION);
export const testAllApplications = createThunk(TEST_ALL_APPLICATIONS);
export const bulkEditApplications = createThunk(BULK_EDIT_APPLICATIONS);
export const bulkDeleteApplications = createThunk(BULK_DELETE_APPLICATIONS);
export const setManageApplicationsSort = createAction(SET_MANAGE_APPLICATIONS_SORT);
export const setApplicationValue = createAction(SET_APPLICATION_VALUE, (payload) => {
return {
@@ -88,7 +92,14 @@ export default {
isTesting: false,
isTestingAll: false,
items: [],
pendingChanges: {}
pendingChanges: {},
sortKey: 'name',
sortDirection: sortDirections.ASCENDING,
sortPredicates: {
name: function(item) {
return item.name.toLowerCase();
}
}
},
//
@@ -121,7 +132,10 @@ export default {
return selectedSchema;
});
}
},
[SET_MANAGE_APPLICATIONS_SORT]: createSetClientSideCollectionSortReducer(section)
}
};

View File

@@ -1,4 +1,5 @@
import { createAction } from 'redux-actions';
import { sortDirections } from 'Helpers/Props';
import createBulkEditItemHandler from 'Store/Actions/Creators/createBulkEditItemHandler';
import createBulkRemoveItemHandler from 'Store/Actions/Creators/createBulkRemoveItemHandler';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
@@ -7,6 +8,7 @@ import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHand
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler';
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
import createSetClientSideCollectionSortReducer from 'Store/Actions/Creators/Reducers/createSetClientSideCollectionSortReducer';
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import { createThunk } from 'Store/thunks';
@@ -34,6 +36,7 @@ export const CANCEL_TEST_DOWNLOAD_CLIENT = 'settings/downloadClients/cancelTestD
export const TEST_ALL_DOWNLOAD_CLIENTS = 'settings/downloadClients/testAllDownloadClients';
export const BULK_EDIT_DOWNLOAD_CLIENTS = 'settings/downloadClients/bulkEditDownloadClients';
export const BULK_DELETE_DOWNLOAD_CLIENTS = 'settings/downloadClients/bulkDeleteDownloadClients';
export const SET_MANAGE_DOWNLOAD_CLIENTS_SORT = 'settings/downloadClients/setManageDownloadClientsSort';
//
// Action Creators
@@ -50,6 +53,7 @@ export const cancelTestDownloadClient = createThunk(CANCEL_TEST_DOWNLOAD_CLIENT)
export const testAllDownloadClients = createThunk(TEST_ALL_DOWNLOAD_CLIENTS);
export const bulkEditDownloadClients = createThunk(BULK_EDIT_DOWNLOAD_CLIENTS);
export const bulkDeleteDownloadClients = createThunk(BULK_DELETE_DOWNLOAD_CLIENTS);
export const setManageDownloadClientsSort = createAction(SET_MANAGE_DOWNLOAD_CLIENTS_SORT);
export const setDownloadClientValue = createAction(SET_DOWNLOAD_CLIENT_VALUE, (payload) => {
return {
@@ -89,7 +93,14 @@ export default {
isTesting: false,
isTestingAll: false,
items: [],
pendingChanges: {}
pendingChanges: {},
sortKey: 'name',
sortDirection: sortDirections.ASCENDING,
sortPredicates: {
name: function(item) {
return item.name.toLowerCase();
}
}
},
//
@@ -147,7 +158,10 @@ export default {
return selectedSchema;
});
}
},
[SET_MANAGE_DOWNLOAD_CLIENTS_SORT]: createSetClientSideCollectionSortReducer(section)
}
};

View File

@@ -1,6 +1,6 @@
import _ from 'lodash';
import { createAction } from 'redux-actions';
import { sortDirections } from 'Helpers/Props';
import { filterTypePredicates, sortDirections } from 'Helpers/Props';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
@@ -69,6 +69,28 @@ export const filterPredicates = {
item.fields.find((field) => field.name === 'vipExpiration')?.value ?? null;
return dateFilterPredicate(vipExpiration, filterValue, type);
},
categories: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const { categories = [] } = item.capabilities || {};
const categoryList = categories
.filter((category) => category.id < 100000)
.reduce((acc, element) => {
acc.push(element.id);
if (element.subCategories && element.subCategories.length > 0) {
element.subCategories.forEach((subCat) => {
acc.push(subCat.id);
});
}
return acc;
}, []);
return predicate(categoryList, filterValue);
}
};
@@ -203,7 +225,13 @@ export const reducers = createHandleActions({
delete selectedSchema.name;
selectedSchema.fields = selectedSchema.fields.map((field) => {
return { ...field };
const newField = { ...field };
if (newField.privacy === 'apiKey' || newField.privacy === 'password') {
newField.value = '';
}
return newField;
});
newState.selectedSchema = selectedSchema;

View File

@@ -186,6 +186,12 @@ export const defaultState = {
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.APP_PROFILE
},
{
name: 'categories',
label: () => translate('Categories'),
type: filterBuilderTypes.ARRAY,
valueType: filterBuilderValueTypes.CATEGORY
},
{
name: 'tags',
label: () => translate('Tags'),

View File

@@ -22,9 +22,9 @@ class About extends Component {
isNetCore,
isDocker,
runtimeVersion,
migrationVersion,
databaseVersion,
databaseType,
migrationVersion,
appData,
startupPath,
mode,
@@ -66,13 +66,13 @@ class About extends Component {
}
<DescriptionListItem
title={translate('DBMigration')}
data={migrationVersion}
title={translate('Database')}
data={`${titleCase(databaseType)} ${databaseVersion}`}
/>
<DescriptionListItem
title={translate('Database')}
data={`${titleCase(databaseType)} ${databaseVersion}`}
title={translate('DatabaseMigration')}
data={migrationVersion}
/>
<DescriptionListItem

View File

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

View File

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

View File

@@ -0,0 +1,151 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Notifications.Email;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class email_encryptionFixture : MigrationTest<email_encryption>
{
[Test]
public void should_convert_do_not_require_encryption_to_auto()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Notifications").Row(new
{
OnGrab = true,
OnHealthIssue = true,
IncludeHealthWarnings = true,
Name = "Mail Prowlarr",
Implementation = "Email",
Tags = "[]",
Settings = new EmailSettings38
{
Server = "smtp.gmail.com",
Port = 563,
To = new List<string> { "dont@email.me" },
RequireEncryption = false
}.ToJson(),
ConfigContract = "EmailSettings"
});
});
var items = db.Query<NotificationDefinition39>("SELECT * FROM \"Notifications\"");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Email");
items.First().ConfigContract.Should().Be("EmailSettings");
items.First().Settings.UseEncryption.Should().Be((int)EmailEncryptionType.Preferred);
}
[Test]
public void should_convert_require_encryption_to_always()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Notifications").Row(new
{
OnGrab = true,
OnHealthIssue = true,
IncludeHealthWarnings = true,
Name = "Mail Prowlarr",
Implementation = "Email",
Tags = "[]",
Settings = new EmailSettings38
{
Server = "smtp.gmail.com",
Port = 563,
To = new List<string> { "dont@email.me" },
RequireEncryption = true
}.ToJson(),
ConfigContract = "EmailSettings"
});
});
var items = db.Query<NotificationDefinition39>("SELECT * FROM \"Notifications\"");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Email");
items.First().ConfigContract.Should().Be("EmailSettings");
items.First().Settings.UseEncryption.Should().Be((int)EmailEncryptionType.Always);
}
[Test]
public void should_use_defaults_when_settings_are_empty()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Notifications").Row(new
{
OnGrab = true,
OnHealthIssue = true,
IncludeHealthWarnings = true,
Name = "Mail Prowlarr",
Implementation = "Email",
Tags = "[]",
Settings = new { }.ToJson(),
ConfigContract = "EmailSettings"
});
});
var items = db.Query<NotificationDefinition39>("SELECT * FROM \"Notifications\"");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Email");
items.First().ConfigContract.Should().Be("EmailSettings");
items.First().Settings.UseEncryption.Should().Be((int)EmailEncryptionType.Preferred);
}
}
public class NotificationDefinition39
{
public int Id { get; set; }
public string Implementation { get; set; }
public string ConfigContract { get; set; }
public EmailSettings39 Settings { get; set; }
public string Name { get; set; }
public bool OnGrab { get; set; }
public bool OnHealthIssue { get; set; }
public bool OnHealthRestored { get; set; }
public bool OnApplicationUpdate { get; set; }
public bool SupportsOnGrab { get; set; }
public bool IncludeManualGrabs { get; set; }
public bool SupportsOnHealthIssue { get; set; }
public bool SupportsOnHealthRestored { get; set; }
public bool IncludeHealthWarnings { get; set; }
public bool SupportsOnApplicationUpdate { get; set; }
public List<int> Tags { get; set; }
}
public class EmailSettings38
{
public string Server { get; set; }
public int Port { get; set; }
public bool RequireEncryption { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string From { get; set; }
public IEnumerable<string> To { get; set; }
public IEnumerable<string> Cc { get; set; }
public IEnumerable<string> Bcc { get; set; }
}
public class EmailSettings39
{
public string Server { get; set; }
public int Port { get; set; }
public int UseEncryption { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string From { get; set; }
public IEnumerable<string> To { get; set; }
public IEnumerable<string> Cc { get; set; }
public IEnumerable<string> Bcc { get; set; }
}
}

View File

@@ -0,0 +1,111 @@
using System;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Notifications.Email;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.NotificationTests.EmailTests
{
[TestFixture]
public class EmailSettingsValidatorFixture : CoreTest<EmailSettingsValidator>
{
private EmailSettings _emailSettings;
private TestValidator<EmailSettings> _validator;
[SetUp]
public void Setup()
{
_validator = new TestValidator<EmailSettings>
{
v => v.RuleFor(s => s).SetValidator(Subject)
};
_emailSettings = Builder<EmailSettings>.CreateNew()
.With(s => s.Server = "someserver")
.With(s => s.Port = 567)
.With(s => s.UseEncryption = (int)EmailEncryptionType.Always)
.With(s => s.From = "dont@email.me")
.With(s => s.To = new string[] { "dont@email.me" })
.Build();
}
[Test]
public void should_be_valid_if_all_settings_valid()
{
_validator.Validate(_emailSettings).IsValid.Should().BeTrue();
}
[Test]
public void should_not_be_valid_if_port_is_out_of_range()
{
_emailSettings.Port = 900000;
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
[Test]
public void should_not_be_valid_if_server_is_empty()
{
_emailSettings.Server = "";
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
[Test]
public void should_not_be_valid_if_from_is_empty()
{
_emailSettings.From = "";
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
[TestCase("prowlarr")]
[TestCase("email.me")]
[Ignore("Allowed coz some email servers allow arbitrary source, we probably need to support 'Name <email>' syntax")]
public void should_not_be_valid_if_from_is_invalid(string email)
{
_emailSettings.From = email;
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
[TestCase("prowlarr")]
[TestCase("email.me")]
public void should_not_be_valid_if_to_is_invalid(string email)
{
_emailSettings.To = new string[] { email };
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
[TestCase("prowlarr")]
[TestCase("email.me")]
public void should_not_be_valid_if_cc_is_invalid(string email)
{
_emailSettings.Cc = new string[] { email };
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
[TestCase("prowlarr")]
[TestCase("email.me")]
public void should_not_be_valid_if_bcc_is_invalid(string email)
{
_emailSettings.Bcc = new string[] { email };
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
[Test]
public void should_not_be_valid_if_to_bcc_cc_are_all_empty()
{
_emailSettings.To = Array.Empty<string>();
_emailSettings.Cc = Array.Empty<string>();
_emailSettings.Bcc = Array.Empty<string>();
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,69 @@
using System.Data;
using System.Text.RegularExpressions;
using FluentMigrator;
using NLog;
using NzbDrone.Common.Instrumentation;
namespace NzbDrone.Core.Datastore.Migration
{
[Maintenance(MigrationStage.BeforeAll, TransactionBehavior.None)]
public class DatabaseEngineVersionCheck : FluentMigrator.Migration
{
protected readonly Logger _logger;
public DatabaseEngineVersionCheck()
{
_logger = NzbDroneLogger.GetLogger(this);
}
public override void Up()
{
IfDatabase("sqlite").Execute.WithConnection(LogSqliteVersion);
IfDatabase("postgres").Execute.WithConnection(LogPostgresVersion);
}
public override void Down()
{
// No-op
}
private void LogSqliteVersion(IDbConnection conn, IDbTransaction tran)
{
using (var versionCmd = conn.CreateCommand())
{
versionCmd.Transaction = tran;
versionCmd.CommandText = "SELECT sqlite_version();";
using (var reader = versionCmd.ExecuteReader())
{
while (reader.Read())
{
var version = reader.GetString(0);
_logger.Info("SQLite {0}", version);
}
}
}
}
private void LogPostgresVersion(IDbConnection conn, IDbTransaction tran)
{
using (var versionCmd = conn.CreateCommand())
{
versionCmd.Transaction = tran;
versionCmd.CommandText = "SHOW server_version";
using (var reader = versionCmd.ExecuteReader())
{
while (reader.Read())
{
var version = reader.GetString(0);
var cleanVersion = Regex.Replace(version, @"\(.*?\)", "");
_logger.Info("Postgres {0}", cleanVersion);
}
}
}
}
}
}

View File

@@ -0,0 +1,50 @@
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(039)]
public class email_encryption : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(ChangeEncryption);
}
private void ChangeEncryption(IDbConnection conn, IDbTransaction tran)
{
var updated = new List<object>();
using (var getEmailCmd = conn.CreateCommand())
{
getEmailCmd.Transaction = tran;
getEmailCmd.CommandText = "SELECT \"Id\", \"Settings\" FROM \"Notifications\" WHERE \"Implementation\" = 'Email'";
using (var reader = getEmailCmd.ExecuteReader())
{
while (reader.Read())
{
var id = reader.GetInt32(0);
var settings = Json.Deserialize<JObject>(reader.GetString(1));
settings["useEncryption"] = settings.Value<bool?>("requireEncryption") ?? false ? 1 : 0;
settings["requireEncryption"] = null;
updated.Add(new
{
Settings = settings.ToJson(),
Id = id
});
}
}
}
var updateSql = "UPDATE \"Notifications\" SET \"Settings\" = @Settings WHERE \"Id\" = @Id";
conn.Execute(updateSql, updated, transaction: tran);
}
}
}

View File

@@ -42,12 +42,13 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
serviceProvider = new ServiceCollection()
.AddLogging(b => b.AddNLog())
.AddFluentMigratorCore()
.Configure<RunnerOptions>(cfg => cfg.IncludeUntaggedMaintenances = true)
.ConfigureRunner(
builder => builder
.AddPostgres()
.AddNzbDroneSQLite()
.WithGlobalConnectionString(connectionString)
.WithMigrationsIn(Assembly.GetExecutingAssembly()))
.ScanIn(Assembly.GetExecutingAssembly()).For.All())
.Configure<TypeFilterOptions>(opt => opt.Namespace = "NzbDrone.Core.Datastore.Migration")
.Configure<ProcessorOptions>(opt =>
{

View File

@@ -32,13 +32,13 @@ namespace NzbDrone.Core.Download.Clients.Aria2
[FieldDefinition(1, Label = "Port", Type = FieldType.Number)]
public int Port { get; set; }
[FieldDefinition(2, Label = "XML RPC Path", Type = FieldType.Textbox)]
[FieldDefinition(2, Label = "XmlRpcPath", Type = FieldType.Textbox)]
public string RpcPath { get; set; }
[FieldDefinition(3, Label = "Use SSL", Type = FieldType.Checkbox)]
[FieldDefinition(3, Label = "UseSsl", Type = FieldType.Checkbox)]
public bool UseSsl { get; set; }
[FieldDefinition(4, Label = "Secret token", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
[FieldDefinition(4, Label = "SecretToken", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string SecretToken { get; set; }
[FieldDefinition(5, Label = "Directory", Type = FieldType.Textbox, HelpText = "DownloadClientAriaSettingsDirectoryHelpText")]

View File

@@ -27,15 +27,16 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
private static readonly TorrentBlackholeSettingsValidator Validator = new TorrentBlackholeSettingsValidator();
[FieldDefinition(0, Label = "Torrent Folder", Type = FieldType.Path, HelpText = "Folder in which Prowlarr will store the .torrent file")]
[FieldDefinition(0, Label = "TorrentBlackholeTorrentFolder", Type = FieldType.Path, HelpText = "BlackholeFolderHelpText")]
[FieldToken(TokenField.HelpText, "TorrentBlackholeTorrentFolder", "extension", ".torrent")]
public string TorrentFolder { get; set; }
[DefaultValue(false)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
[FieldDefinition(1, Label = "Save Magnet Files", Type = FieldType.Checkbox, HelpText = "Save a .magnet file with the magnet link if no .torrent file is available (only useful if the download client supports .magnet files)")]
[FieldDefinition(1, Label = "TorrentBlackholeSaveMagnetFiles", Type = FieldType.Checkbox, HelpText = "TorrentBlackholeSaveMagnetFilesHelpText")]
public bool SaveMagnetFiles { get; set; }
[FieldDefinition(2, Label = "Save Magnet Files", Type = FieldType.Textbox, HelpText = "Extension to use for magnet links, defaults to '.magnet'")]
[FieldDefinition(2, Label = "TorrentBlackholeSaveMagnetFilesExtension", Type = FieldType.Textbox, HelpText = "TorrentBlackholeSaveMagnetFilesExtensionHelpText")]
public string MagnetFileExtension { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -18,7 +18,8 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
{
private static readonly UsenetBlackholeSettingsValidator Validator = new UsenetBlackholeSettingsValidator();
[FieldDefinition(0, Label = "Nzb Folder", Type = FieldType.Path, HelpText = "Folder in which Prowlarr will store the .nzb file")]
[FieldDefinition(0, Label = "UsenetBlackholeNzbFolder", Type = FieldType.Path, HelpText = "BlackholeFolderHelpText")]
[FieldToken(TokenField.HelpText, "UsenetBlackholeNzbFolder", "extension", ".nzb")]
public string NzbFolder { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -34,22 +34,24 @@ namespace NzbDrone.Core.Download.Clients.Deluge
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Deluge")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Deluge")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the deluge json url, see http://[host]:[port]/[urlBase]/json")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientDelugeSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/json")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback Category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(5, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(6, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing items")]
[FieldDefinition(6, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "DownloadClientSettingsPriorityItemHelpText")]
public int Priority { get; set; }
[FieldDefinition(7, Label = "Add Paused", Type = FieldType.Checkbox)]
[FieldDefinition(7, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)]
public bool AddPaused { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -36,7 +36,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Download Station")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Download Station")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -45,10 +46,10 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
[FieldDefinition(4, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
[FieldDefinition(5, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, HelpText = "Optional shared folder to put downloads into, leave blank to use the default Download Station location")]
[FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, HelpText = "DownloadClientDownloadStationSettingsDirectoryHelpText")]
public string TvDirectory { get; set; }
public DownloadStationSettings()

View File

@@ -40,10 +40,12 @@ namespace NzbDrone.Core.Download.Clients.Flood
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Flood")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Flood")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, HelpText = "Optionally adds a prefix to Flood API, such as [protocol]://[host]:[port]/[urlBase]api")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, HelpText = "DownloadClientFloodSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "[protocol]://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -52,16 +54,16 @@ namespace NzbDrone.Core.Download.Clients.Flood
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Destination", Type = FieldType.Textbox, HelpText = "Manually specifies download destination")]
[FieldDefinition(6, Label = "Destination", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDestinationHelpText")]
public string Destination { get; set; }
[FieldDefinition(7, Label = "Tags", Type = FieldType.Tag, HelpText = "Initial tags of a download. To be recognized, a download must have all initial tags. This avoids conflicts with unrelated downloads.")]
[FieldDefinition(7, Label = "Tags", Type = FieldType.Tag, HelpText = "DownloadClientFloodSettingsTagsHelpText")]
public IEnumerable<string> Tags { get; set; }
[FieldDefinition(8, Label = "Additional Tags", Type = FieldType.Select, SelectOptions = typeof(AdditionalTags), HelpText = "Adds properties of media as tags. Hints are examples.", Advanced = true)]
[FieldDefinition(8, Label = "DownloadClientFloodSettingsAdditionalTags", Type = FieldType.Select, SelectOptions = typeof(AdditionalTags), HelpText = "DownloadClientFloodSettingsAdditionalTagsHelpText", Advanced = true)]
public IEnumerable<int> AdditionalTags { get; set; }
[FieldDefinition(9, Label = "Add Paused", Type = FieldType.Checkbox)]
[FieldDefinition(9, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)]
public bool AddPaused { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -46,34 +46,39 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
ApiUrl = "/api/v1/";
}
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox, HelpText = "Hostname or host IP address of the Freebox, defaults to 'mafreebox.freebox.fr' (will only work if on same network)")]
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox, HelpText = "DownloadClientFreeboxSettingsHostHelpText")]
[FieldToken(TokenField.HelpText, "Host", "url", "mafreebox.freebox.fr")]
public string Host { get; set; }
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox, HelpText = "Port used to access Freebox interface, defaults to '443'")]
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox, HelpText = "DownloadClientFreeboxSettingsPortHelpText")]
[FieldToken(TokenField.HelpText, "Port", "port", 443)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secured connection when connecting to Freebox API")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Freebox API")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "API URL", Type = FieldType.Textbox, Advanced = true, HelpText = "Define Freebox API base URL with API version, eg http://[host]:[port]/[api_base_url]/[api_version]/, defaults to '/api/v1/'")]
[FieldDefinition(3, Label = "DownloadClientFreeboxSettingsApiUrl", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientFreeboxSettingsApiUrlHelpText")]
[FieldToken(TokenField.HelpText, "DownloadClientFreeboxSettingsApiUrl", "url", "http://[host]:[port]/[api_base_url]/[api_version]/")]
[FieldToken(TokenField.HelpText, "DownloadClientFreeboxSettingsApiUrl", "defaultApiUrl", "/api/v1/")]
public string ApiUrl { get; set; }
[FieldDefinition(4, Label = "App ID", Type = FieldType.Textbox, HelpText = "App ID given when creating access to Freebox API (ie 'app_id')")]
[FieldDefinition(4, Label = "DownloadClientFreeboxSettingsAppId", Type = FieldType.Textbox, HelpText = "DownloadClientFreeboxSettingsAppIdHelpText")]
public string AppId { get; set; }
[FieldDefinition(5, Label = "App Token", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "App token retrieved when creating access to Freebox API (ie 'app_token')")]
[FieldDefinition(5, Label = "DownloadClientFreeboxSettingsAppToken", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "DownloadClientFreeboxSettingsAppTokenHelpText")]
public string AppToken { get; set; }
[FieldDefinition(6, Label = "Destination Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Freebox download location")]
[FieldDefinition(6, Label = "Destination", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsDestinationHelpText")]
public string DestinationDirectory { get; set; }
[FieldDefinition(7, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Prowlarr avoids conflicts with unrelated non-Prowlarr downloads (will create a [category] subdirectory in the output directory)")]
[FieldDefinition(7, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(8, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "Priority to use when grabbing")]
[FieldDefinition(8, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(FreeboxDownloadPriority), HelpText = "DownloadClientSettingsPriorityItemHelpText")]
public int Priority { get; set; }
[FieldDefinition(9, Label = "Add Paused", Type = FieldType.Checkbox)]
[FieldDefinition(9, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)]
public bool AddPaused { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -39,10 +39,13 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Hadouken")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Hadouken")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the Hadouken url, e.g. http://[host]:[port]/[urlBase]/api")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "clientName", "Hadouken")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -51,7 +54,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release.")]
[FieldDefinition(6, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -41,16 +41,18 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the NZBVortex url, e.g. http://[host]:[port]/[urlBase]/api")]
[FieldDefinition(2, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "clientName", "NZBVortex")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(3, Label = "API Key", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(3, Label = "ApiKey", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(4, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(4, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(5, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing items")]
[FieldDefinition(5, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "DownloadClientSettingsPriorityItemHelpText")]
public int Priority { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -41,10 +41,13 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to NZBGet")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "NZBGet")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the NZBGet url, e.g. http://[host]:[port]/[urlBase]/jsonrpc")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "clientName", "NZBGet")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/jsonrpc")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -53,13 +56,13 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(6, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(7, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority for items added from Prowlarr")]
[FieldDefinition(7, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "DownloadClientSettingsPriorityItemHelpText")]
public int Priority { get; set; }
[FieldDefinition(8, Label = "Add Paused", Type = FieldType.Checkbox, HelpText = "This option requires at least NZBGet version 16.0")]
[FieldDefinition(8, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox, HelpText = "DownloadClientNzbgetSettingsAddPausedHelpText")]
public bool AddPaused { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -19,10 +19,10 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
{
private static readonly PneumaticSettingsValidator Validator = new PneumaticSettingsValidator();
[FieldDefinition(0, Label = "Nzb Folder", Type = FieldType.Path, HelpText = "This folder will need to be reachable from XBMC")]
[FieldDefinition(0, Label = "DownloadClientPneumaticSettingsNzbFolder", Type = FieldType.Path, HelpText = "DownloadClientPneumaticSettingsNzbFolderHelpText")]
public string NzbFolder { get; set; }
[FieldDefinition(1, Label = "Strm Folder", Type = FieldType.Path, HelpText = ".strm files in this folder will be import by drone")]
[FieldDefinition(1, Label = "DownloadClientPneumaticSettingsStrmFolder", Type = FieldType.Path, HelpText = "DownloadClientPneumaticSettingsStrmFolderHelpText")]
public string StrmFolder { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -35,10 +35,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use a secure connection. See Options -> Web UI -> 'Use HTTPS instead of HTTP' in qBittorrent.")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientQbittorrentSettingsUseSslHelpText")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the qBittorrent url, e.g. http://[host]:[port]/[urlBase]/api")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "clientName", "qBittorrent")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -47,22 +49,22 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(6, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(7, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing items")]
[FieldDefinition(7, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "DownloadClientSettingsPriorityItemHelpText")]
public int Priority { get; set; }
[FieldDefinition(8, Label = "Initial State", Type = FieldType.Select, SelectOptions = typeof(QBittorrentState), HelpText = "Initial state for torrents added to qBittorrent")]
[FieldDefinition(8, Label = "DownloadClientSettingsInitialState", Type = FieldType.Select, SelectOptions = typeof(QBittorrentState), HelpText = "DownloadClientQbittorrentSettingsInitialStateHelpText")]
public int InitialState { get; set; }
[FieldDefinition(9, Label = "Sequential Order", Type = FieldType.Checkbox, HelpText = "Download in sequential order (qBittorrent 4.1.0+)")]
[FieldDefinition(9, Label = "DownloadClientQbittorrentSettingsSequentialOrder", Type = FieldType.Checkbox, HelpText = "DownloadClientQbittorrentSettingsSequentialOrderHelpText")]
public bool SequentialOrder { get; set; }
[FieldDefinition(10, Label = "First and Last First", Type = FieldType.Checkbox, HelpText = "Download first and last pieces first (qBittorrent 4.1.0+)")]
[FieldDefinition(10, Label = "DownloadClientQbittorrentSettingsFirstAndLastFirst", Type = FieldType.Checkbox, HelpText = "DownloadClientQbittorrentSettingsFirstAndLastFirstHelpText")]
public bool FirstAndLast { get; set; }
[FieldDefinition(13, Label = "DownloadClientQbittorrentSettingsContentLayout", Type = FieldType.Select, SelectOptions = typeof(QBittorrentContentLayout), HelpText = "DownloadClientQbittorrentSettingsContentLayoutHelpText")]
[FieldDefinition(11, Label = "DownloadClientQbittorrentSettingsContentLayout", Type = FieldType.Select, SelectOptions = typeof(QBittorrentContentLayout), HelpText = "DownloadClientQbittorrentSettingsContentLayoutHelpText")]
public int ContentLayout { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -50,13 +50,16 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Sabnzbd")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Sabnzbd")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the Sabnzbd url, e.g. http://[host]:[port]/[urlBase]/api")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "clientName", "Sabnzbd")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "API Key", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(4, Label = "ApiKey", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(5, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -65,10 +68,10 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
[FieldDefinition(6, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(7, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(7, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(8, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing items")]
[FieldDefinition(8, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "DownloadClientSettingsPriorityItemHelpText")]
public int Priority { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -41,10 +41,14 @@ namespace NzbDrone.Core.Download.Clients.Transmission
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Transmission")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "Transmission")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the transmission rpc url, eg http://[host]:[port]/[urlBase]/rpc, defaults to '/transmission/'")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientTransmissionSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "clientName", "Transmission")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/rpc")]
[FieldToken(TokenField.HelpText, "UrlBase", "defaultUrl", "/transmission/")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -53,16 +57,16 @@ namespace NzbDrone.Core.Download.Clients.Transmission
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
[FieldDefinition(6, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategorySubFolderHelpText")]
public string Category { get; set; }
[FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Transmission location")]
[FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientTransmissionSettingsDirectoryHelpText")]
public string Directory { get; set; }
[FieldDefinition(8, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing items")]
[FieldDefinition(8, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "DownloadClientSettingsPriorityItemHelpText")]
public int Priority { get; set; }
[FieldDefinition(9, Label = "Add Paused", Type = FieldType.Checkbox)]
[FieldDefinition(9, Label = "DownloadClientSettingsAddPaused", Type = FieldType.Checkbox)]
public bool AddPaused { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -36,10 +36,13 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to ruTorrent")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "rTorrent")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Path", Type = FieldType.Textbox, HelpText = "Path to the XMLRPC endpoint, see http(s)://[host]:[port]/[urlPath]. When using ruTorrent this usually is RPC2 or (path to ruTorrent)/plugins/rpc/rpc.php")]
[FieldDefinition(3, Label = "DownloadClientRTorrentSettingsUrlPath", Type = FieldType.Textbox, HelpText = "DownloadClientRTorrentSettingsUrlPathHelpText")]
[FieldToken(TokenField.HelpText, "DownloadClientRTorrentSettingsUrlPath", "url", "http(s)://[host]:[port]/[urlPath]")]
[FieldToken(TokenField.HelpText, "DownloadClientRTorrentSettingsUrlPath", "url2", "/plugins/rpc/rpc.php")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -48,16 +51,16 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional.")]
[FieldDefinition(6, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default rTorrent location")]
[FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientRTorrentSettingsDirectoryHelpText")]
public string Directory { get; set; }
[FieldDefinition(8, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing items")]
[FieldDefinition(8, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "DownloadClientSettingsPriorityItemHelpText")]
public int Priority { get; set; }
[FieldDefinition(9, Label = "Add Stopped", Type = FieldType.Checkbox, HelpText = "Enabling will add torrents and magnets to ruTorrent in a stopped state")]
[FieldDefinition(9, Label = "DownloadClientRTorrentSettingsAddStopped", Type = FieldType.Checkbox, HelpText = "DownloadClientRTorrentSettingsAddStoppedHelpText")]
public bool AddStopped { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -34,10 +34,13 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public int Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to uTorrent")]
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "DownloadClientSettingsUseSslHelpText")]
[FieldToken(TokenField.HelpText, "UseSsl", "clientName", "uTorrent")]
public bool UseSsl { get; set; }
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the uTorrent url, e.g. http://[host]:[port]/[urlBase]/api")]
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "DownloadClientSettingsUrlBaseHelpText")]
[FieldToken(TokenField.HelpText, "UrlBase", "clientName", "uTorrent")]
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/api")]
public string UrlBase { get; set; }
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
@@ -46,13 +49,14 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(6, Label = "Default Category", Type = FieldType.Textbox, HelpText = "Default fallback category if no mapped category exists for a release. Adding a category specific to Prowlarr avoids conflicts with unrelated downloads, but it's optional")]
[FieldDefinition(6, Label = "DefaultCategory", Type = FieldType.Textbox, HelpText = "DownloadClientSettingsDefaultCategoryHelpText")]
public string Category { get; set; }
[FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing items")]
[FieldDefinition(7, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "DownloadClientSettingsPriorityItemHelpText")]
public int Priority { get; set; }
[FieldDefinition(8, Label = "Initial State", Type = FieldType.Select, SelectOptions = typeof(UTorrentState), HelpText = "Initial state for torrents added to uTorrent")]
[FieldDefinition(8, Label = "DownloadClientSettingsInitialState", Type = FieldType.Select, SelectOptions = typeof(UTorrentState), HelpText = "DownloadClientSettingsInitialStateHelpText")]
[FieldToken(TokenField.HelpText, "DownloadClientSettingsInitialState", "clientName", "uTorrent")]
public int IntialState { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -115,7 +115,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
MaxTimeout = maxTimeout,
Proxy = new FlareSolverrProxy
{
Url = proxyUrl?.AbsoluteUri
Url = proxyUrl?.OriginalString
}
};
}
@@ -141,7 +141,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
MaxTimeout = maxTimeout,
Proxy = new FlareSolverrProxy
{
Url = proxyUrl?.AbsoluteUri
Url = proxyUrl?.OriginalString
}
};
}
@@ -206,17 +206,13 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
private Uri GetProxyUri(HttpProxySettings proxySettings)
{
switch (proxySettings.Type)
return proxySettings.Type switch
{
case ProxyType.Http:
return new Uri("http://" + proxySettings.Host + ":" + proxySettings.Port);
case ProxyType.Socks4:
return new Uri("socks4://" + proxySettings.Host + ":" + proxySettings.Port);
case ProxyType.Socks5:
return new Uri("socks5://" + proxySettings.Host + ":" + proxySettings.Port);
default:
return null;
}
ProxyType.Http => new Uri("http://" + proxySettings.Host + ":" + proxySettings.Port),
ProxyType.Socks4 => new Uri("socks4://" + proxySettings.Host + ":" + proxySettings.Port),
ProxyType.Socks5 => new Uri("socks5://" + proxySettings.Host + ":" + proxySettings.Port),
_ => null
};
}
private class FlareSolverrRequest

View File

@@ -154,9 +154,9 @@ public class AlphaRatioParser : GazelleParser
public class AlphaRatioSettings : GazelleSettings
{
[FieldDefinition(6, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search freeleech torrents only")]
[FieldDefinition(6, Label = "IndexerSettingsFreeleechOnly", Type = FieldType.Checkbox, HelpText = "IndexerAlphaRatioSettingsFreeleechOnlyHelpText")]
public bool FreeleechOnly { get; set; }
[FieldDefinition(7, Label = "Exclude Scene", Type = FieldType.Checkbox, HelpText = "Exclude Scene torrents from results")]
[FieldDefinition(7, Label = "IndexerAlphaRatioSettingsExcludeScene", Type = FieldType.Checkbox, HelpText = "IndexerAlphaRatioSettingsExcludeSceneHelpText")]
public bool ExcludeScene { get; set; }
}

View File

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

View File

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

View File

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

View File

@@ -70,7 +70,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{
_logger.Warn(ex, "Failed to authenticate with Avistaz");
var jsonResponse = STJson.Deserialize<AvistazErrorResponse>(ex.Response.Content);
STJson.TryDeserialize<AvistazErrorResponse>(ex.Response.Content, out var jsonResponse);
throw new IndexerAuthException(jsonResponse?.Message ?? "Unauthorized request to indexer");
}
}
@@ -98,8 +98,8 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{
_logger.Warn(ex, "Unauthorized request to indexer");
var jsonResponse = new HttpResponse<AvistazErrorResponse>(ex.Response);
return new ValidationFailure(string.Empty, jsonResponse.Resource?.Message ?? "Unauthorized request to indexer");
STJson.TryDeserialize<AvistazErrorResponse>(ex.Response.Content, out var jsonResponse);
return new ValidationFailure(string.Empty, jsonResponse?.Message ?? "Unauthorized request to indexer");
}
_logger.Warn(ex, "Unable to connect to indexer");
@@ -134,7 +134,10 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
var response = await ExecuteAuth(authLoginRequest);
var authResponse = STJson.Deserialize<AvistazAuthResponse>(response.Content);
if (!STJson.TryDeserialize<AvistazAuthResponse>(response.Content, out var authResponse))
{
throw new Exception("Invalid response from AvistaZ, the response is not valid JSON");
}
return authResponse.Token;
}

View File

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

View File

@@ -334,25 +334,25 @@ namespace NzbDrone.Core.Indexers.Definitions
SearchTypes = Array.Empty<int>();
}
[FieldDefinition(2, Label = "API Key", HelpText = "API Key from the Site (Found in My Security => API Key)", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(2, Label = "ApiKey", HelpText = "IndexerBeyondHDSettingsApiKeyHelpText", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "RSS Key", HelpText = "RSS Key from the Site (Found in My Security => RSS Key)", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(3, Label = "IndexerSettingsRssKey", HelpText = "IndexerBeyondHDSettingsRssKeyHelpText", Privacy = PrivacyLevel.ApiKey)]
public string RssKey { get; set; }
[FieldDefinition(4, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search freeleech only")]
[FieldDefinition(4, Label = "IndexerSettingsFreeleechOnly", Type = FieldType.Checkbox, HelpText = "IndexerBeyondHDSettingsFreeleechOnlyHelpText")]
public bool FreeleechOnly { get; set; }
[FieldDefinition(5, Label = "Limited Only", Type = FieldType.Checkbox, HelpText = "Search freeleech only (Limited UL)")]
[FieldDefinition(5, Label = "IndexerBeyondHDSettingsLimitedOnly", Type = FieldType.Checkbox, HelpText = "IndexerBeyondHDSettingsLimitedOnlyHelpText")]
public bool LimitedOnly { get; set; }
[FieldDefinition(6, Label = "Refund Only", Type = FieldType.Checkbox, HelpText = "Search refund only")]
[FieldDefinition(6, Label = "IndexerBeyondHDSettingsRefundOnly", Type = FieldType.Checkbox, HelpText = "IndexerBeyondHDSettingsRefundOnlyHelpText")]
public bool RefundOnly { get; set; }
[FieldDefinition(7, Label = "Rewind Only", Type = FieldType.Checkbox, HelpText = "Search rewind only")]
[FieldDefinition(7, Label = "IndexerBeyondHDSettingsRewindOnly", Type = FieldType.Checkbox, HelpText = "IndexerBeyondHDSettingsRewindOnlyHelpText")]
public bool RewindOnly { get; set; }
[FieldDefinition(8, Label = "Search Types", Type = FieldType.TagSelect, SelectOptions = typeof(BeyondHDSearchType), Advanced = true, HelpText = "Select the types of releases that you are interested in. If none selected, all options are used.")]
[FieldDefinition(8, Label = "IndexerBeyondHDSettingsSearchTypes", Type = FieldType.TagSelect, SelectOptions = typeof(BeyondHDSearchType), HelpText = "IndexerBeyondHDSettingsSearchTypesHelpText", Advanced = true)]
public IEnumerable<int> SearchTypes { get; set; }
public override NzbDroneValidationResult Validate()

View File

@@ -32,7 +32,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
var parameters = new BroadcastheNetTorrentQuery();
var searchString = searchCriteria.SearchTerm ?? string.Empty;
var searchTerm = searchCriteria.SearchTerm ?? string.Empty;
var btnResults = searchCriteria.Limit.GetValueOrDefault();
if (btnResults == 0)
@@ -50,9 +50,10 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
{
parameters.Tvrage = $"{searchCriteria.RId}";
}
else if (searchString.IsNotNullOrWhiteSpace())
if (searchTerm.IsNotNullOrWhiteSpace())
{
parameters.Search = searchString.Replace(" ", "%");
parameters.Search = searchTerm.Replace(" ", "%");
}
// If only the season/episode is searched for then change format to match expected format
@@ -84,6 +85,11 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
parameters.Category = "Episode";
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));
}
else if (searchTerm.IsNotNullOrWhiteSpace() && int.TryParse(searchTerm, out _) && (searchCriteria.TvdbId > 0 || searchCriteria.RId > 0))
{
// Disable ID-based searches for episodes with absolute episode number
return new IndexerPageableRequestChain();
}
else
{
// Neither a season only search nor daily nor standard, fall back to query
@@ -104,7 +110,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
var parameters = new BroadcastheNetTorrentQuery();
var searchString = searchCriteria.SearchTerm ?? "";
var searchTerm = searchCriteria.SearchTerm ?? string.Empty;
var btnResults = searchCriteria.Limit.GetValueOrDefault();
if (btnResults == 0)
@@ -114,9 +120,9 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
var btnOffset = searchCriteria.Offset.GetValueOrDefault(0);
if (searchString.IsNotNullOrWhiteSpace())
if (searchTerm.IsNotNullOrWhiteSpace())
{
parameters.Search = searchString.Replace(" ", "%");
parameters.Search = searchTerm.Replace(" ", "%");
}
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));

View File

@@ -23,7 +23,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
BaseSettings.LimitsUnit = (int)IndexerLimitsUnit.Hour;
}
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
public override NzbDroneValidationResult Validate()

View File

@@ -24,13 +24,13 @@ public class FileListSettings : NoAuthTorrentBaseSettings
BaseSettings.LimitsUnit = (int)IndexerLimitsUnit.Hour;
}
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
[FieldDefinition(2, Label = "Username", HelpText = "IndexerFileListSettingsUsernameHelpText", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(3, Label = "Passkey", HelpText = "Site Passkey (This is the alphanumeric string in the tracker url shown in your download client)", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
[FieldDefinition(3, Label = "IndexerSettingsPasskey", HelpText = "IndexerFileListSettingsPasskeyHelpText", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
public string Passkey { get; set; }
[FieldDefinition(4, Label = "Freeleech Only", HelpText = "Search Freeleech torrents only", Type = FieldType.Checkbox)]
[FieldDefinition(4, Label = "IndexerSettingsFreeleechOnly", HelpText = "IndexerFileListSettingsFreeleechOnlyHelpText", Type = FieldType.Checkbox)]
public bool FreeleechOnly { get; set; }
public override NzbDroneValidationResult Validate()

View File

@@ -412,6 +412,8 @@ namespace NzbDrone.Core.Indexers.Definitions
categories = _categories.MapTrackerCatToNewznab(torrent.Value.CategoryId.ToString()).ToArray();
}
Enum.TryParse(torrent.Value.FreeTorrent, true, out GazelleGamesFreeTorrent freeTorrent);
var release = new TorrentInfo
{
Guid = infoUrl,
@@ -426,8 +428,8 @@ namespace NzbDrone.Core.Indexers.Definitions
Peers = torrent.Value.Leechers + torrent.Value.Seeders,
PublishDate = torrent.Value.Time.ToUniversalTime(),
Scene = torrent.Value.Scene == 1,
DownloadVolumeFactor = torrent.Value.FreeTorrent is GazelleGamesFreeTorrent.FreeLeech or GazelleGamesFreeTorrent.Neutral || torrent.Value.LowSeedFL ? 0 : 1,
UploadVolumeFactor = torrent.Value.FreeTorrent == GazelleGamesFreeTorrent.Neutral ? 0 : 1,
DownloadVolumeFactor = freeTorrent is GazelleGamesFreeTorrent.FreeLeech or GazelleGamesFreeTorrent.Neutral || torrent.Value.LowSeedFL ? 0 : 1,
UploadVolumeFactor = freeTorrent == GazelleGamesFreeTorrent.Neutral ? 0 : 1,
MinimumSeedTime = 288000 // Minimum of 3 days and 8 hours (80 hours in total)
};
@@ -535,10 +537,10 @@ namespace NzbDrone.Core.Indexers.Definitions
Passkey = "";
}
[FieldDefinition(2, Label = "API Key", HelpText = "API Key from the Site (Found in Settings => Access Settings)", HelpTextWarning = "Must have User and Torrents permissions", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(2, Label = "ApiKey", HelpText = "IndexerGazelleGamesSettingsApiKeyHelpText", HelpTextWarning = "IndexerGazelleGamesSettingsApiKeyHelpTextWarning", Privacy = PrivacyLevel.ApiKey)]
public string Apikey { get; set; }
[FieldDefinition(3, Label = "Search Group Names", Type = FieldType.Checkbox, HelpText = "Search Group Names Only")]
[FieldDefinition(3, Label = "IndexerGazelleGamesSettingsSearchGroupNames", Type = FieldType.Checkbox, HelpText = "IndexerGazelleGamesSettingsSearchGroupNamesHelpText")]
public bool SearchGroupNames { get; set; }
public string Passkey { get; set; }
@@ -588,7 +590,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public int? Snatched { get; set; }
public int Seeders { get; set; }
public int Leechers { get; set; }
public GazelleGamesFreeTorrent FreeTorrent { get; set; }
public string FreeTorrent { get; set; }
public bool PersonalFL { get; set; }
public bool LowSeedFL { get; set; }

View File

@@ -29,25 +29,25 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
UseFilenames = true;
}
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
[FieldDefinition(2, Label = "Username", HelpText = "IndexerHDBitsSettingsUsernameHelpText", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(3, Label = "API Key", HelpText = "Site API Key", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(3, Label = "IndexerSettingsPasskey", HelpText = "IndexerHDBitsSettingsPasskeyHelpText", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(4, Label = "Codecs", Type = FieldType.Select, SelectOptions = typeof(HdBitsCodec), HelpText = "If unspecified, all options are used.", Advanced = true)]
[FieldDefinition(4, Label = "IndexerHDBitsSettingsCodecs", Type = FieldType.Select, SelectOptions = typeof(HdBitsCodec), HelpText = "IndexerHDBitsSettingsCodecsHelpText", Advanced = true)]
public IEnumerable<int> Codecs { get; set; }
[FieldDefinition(5, Label = "Mediums", Type = FieldType.Select, SelectOptions = typeof(HdBitsMedium), HelpText = "If unspecified, all options are used.", Advanced = true)]
[FieldDefinition(5, Label = "IndexerHDBitsSettingsMediums", Type = FieldType.Select, SelectOptions = typeof(HdBitsMedium), HelpText = "IndexerHDBitsSettingsMediumsHelpText", Advanced = true)]
public IEnumerable<int> Mediums { get; set; }
[FieldDefinition(6, Label = "Origins", Type = FieldType.Select, SelectOptions = typeof(HdBitsOrigin), HelpText = "If unspecified, all options are used.", Advanced = true)]
[FieldDefinition(6, Label = "IndexerHDBitsSettingsOrigins", Type = FieldType.Select, SelectOptions = typeof(HdBitsOrigin), HelpText = "IndexerHDBitsSettingsOriginsHelpText", Advanced = true)]
public IEnumerable<int> Origins { get; set; }
[FieldDefinition(7, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Show freeleech releases only", Advanced = true)]
[FieldDefinition(7, Label = "IndexerSettingsFreeleechOnly", Type = FieldType.Checkbox, HelpText = "IndexerHDBitsSettingsFreeleechOnlyHelpText", Advanced = true)]
public bool FreeleechOnly { get; set; }
[FieldDefinition(8, Label = "Use Filenames", Type = FieldType.Checkbox, HelpText = "Check this option if you want to use torrent filenames as release titles")]
[FieldDefinition(8, Label = "IndexerHDBitsSettingsUseFilenames", Type = FieldType.Checkbox, HelpText = "IndexerHDBitsSettingsUseFilenamesHelpText")]
public bool UseFilenames { get; set; }
public override NzbDroneValidationResult Validate()

View File

@@ -255,7 +255,14 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedTvSearchString}", searchCriteria, searchCriteria.FullImdbId));
var searchTerm = $"{searchCriteria.SanitizedTvSearchString}";
if (searchCriteria.Season > 0 && searchCriteria.Episode.IsNullOrWhiteSpace())
{
searchTerm += "*";
}
pageableRequests.Add(GetPagedRequests(searchTerm.Trim(), searchCriteria, searchCriteria.FullImdbId));
return pageableRequests;
}
@@ -413,10 +420,10 @@ namespace NzbDrone.Core.Indexers.Definitions
{
private static readonly IPTorrentsValidator Validator = new ();
[FieldDefinition(3, Label = "Cookie User-Agent", Type = FieldType.Textbox, HelpText = "User-Agent associated with cookie used from Browser")]
[FieldDefinition(3, Label = "IndexerIPTorrentsSettingsCookieUserAgent", Type = FieldType.Textbox, HelpText = "IndexerIPTorrentsSettingsCookieUserAgentHelpText")]
public string UserAgent { get; set; }
[FieldDefinition(4, Label = "FreeLeech Only", Type = FieldType.Checkbox, HelpText = "Search FreeLeech torrents only")]
[FieldDefinition(4, Label = "IndexerSettingsFreeleechOnly", Type = FieldType.Checkbox, HelpText = "IndexerIPTorrentsSettingsFreeleechOnlyHelpText")]
public bool FreeLeechOnly { get; set; }
public override NzbDroneValidationResult Validate()

View File

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

View File

@@ -293,7 +293,7 @@ namespace NzbDrone.Core.Indexers.Definitions
ApiKey = "";
}
[FieldDefinition(4, Label = "API Key", HelpText = "API Key from User Settings > Api Keys. Key must have List and Download permissions")]
[FieldDefinition(4, Label = "ApiKey", HelpText = "IndexerNebulanceSettingsApiKeyHelpText")]
public string ApiKey { get; set; }
}

View File

@@ -60,16 +60,17 @@ namespace NzbDrone.Core.Indexers.Newznab
[FieldDefinition(0, Label = "URL")]
public string BaseUrl { get; set; }
[FieldDefinition(1, Label = "API Path", HelpText = "Path to the api, usually /api", Advanced = true)]
[FieldDefinition(1, Label = "IndexerSettingsApiPath", HelpText = "IndexerSettingsApiPathHelpText", Advanced = true)]
[FieldToken(TokenField.HelpText, "IndexerSettingsApiPath", "url", "/api")]
public string ApiPath { get; set; }
[FieldDefinition(2, Label = "API Key", HelpText = "Site API Key", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(2, Label = "ApiKey", HelpText = "IndexerNewznabSettingsApiKeyHelpText", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(5, Label = "Additional Parameters", HelpText = "Additional Newznab parameters", Advanced = true)]
[FieldDefinition(5, Label = "IndexerSettingsAdditionalParameters", HelpText = "IndexerNewznabSettingsAdditionalParametersHelpText", Advanced = true)]
public string AdditionalParameters { get; set; }
[FieldDefinition(6, Label = "VIP Expiration", HelpText = "Enter date (yyyy-mm-dd) for VIP Expiration or blank, Prowlarr will notify 1 week from expiration of VIP")]
[FieldDefinition(6, Label = "IndexerSettingsVipExpiration", HelpText = "IndexerNewznabSettingsVipExpirationHelpText")]
public string VipExpiration { get; set; }
[FieldDefinition(7)]

View File

@@ -1187,10 +1187,10 @@ namespace NzbDrone.Core.Indexers.Definitions
ApiKey = "";
}
[FieldDefinition(1, Label = "Base Url", HelpText = "Select which baseurl Prowlarr will use for requests to the site", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls")]
[FieldDefinition(1, Label = "IndexerSettingsBaseUrl", HelpText = "IndexerSettingsBaseUrlHelpText", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "Site API Key")]
[FieldDefinition(2, Label = "ApiKey", HelpText = "IndexerNzbIndexSettingsApiKeyHelpText", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(3)]

View File

@@ -438,7 +438,7 @@ namespace NzbDrone.Core.Indexers.Definitions
UseFreeleechToken = false;
}
[FieldDefinition(2, Label = "API Key", HelpText = "API Key from the Site (Found in Settings => Access Settings)", Privacy = PrivacyLevel.ApiKey)]
[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)]

View File

@@ -18,13 +18,13 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
{
private static readonly PassThePopcornSettingsValidator Validator = new ();
[FieldDefinition(2, Label = "API User", HelpText = "These settings are found in your PassThePopcorn security settings (Edit Profile > Security).", Privacy = PrivacyLevel.UserName)]
[FieldDefinition(2, Label = "IndexerSettingsApiUser", HelpText = "IndexerPassThePopcornSettingsApiUserHelpText", Privacy = PrivacyLevel.UserName)]
public string APIUser { get; set; }
[FieldDefinition(3, Label = "API Key", HelpText = "Site API Key", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(3, Label = "ApiKey", HelpText = "IndexerPassThePopcornSettingsApiKeyHelpText", Privacy = PrivacyLevel.ApiKey)]
public string APIKey { get; set; }
[FieldDefinition(4, Label = "Freeleech Only", HelpText = "Return only freeleech torrents", Type = FieldType.Checkbox)]
[FieldDefinition(4, Label = "IndexerSettingsFreeleechOnly", HelpText = "IndexerPassThePopcornSettingsFreeleechOnlyHelpText", Type = FieldType.Checkbox)]
public bool FreeleechOnly { get; set; }
public override NzbDroneValidationResult Validate()

View File

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

View File

@@ -420,7 +420,7 @@ namespace NzbDrone.Core.Indexers.Definitions
UseFreeleechToken = false;
}
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "API Key from the Site (Found in Settings => Access Settings)")]
[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")]

View File

@@ -101,7 +101,7 @@ public class Shazbat : TorrentIndexerBase<ShazbatSettings>
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
Flags = new List<IndexerFlag>
{

View File

@@ -316,7 +316,7 @@ namespace NzbDrone.Core.Indexers.Definitions
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
public string Password { get; set; }
[FieldDefinition(4, Label = "API Key", Hidden = HiddenType.Hidden)]
[FieldDefinition(4, Label = "ApiKey", Hidden = HiddenType.Hidden)]
public string ApiKey { get; set; }
public override NzbDroneValidationResult Validate()

View File

@@ -6,6 +6,7 @@ using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
@@ -43,12 +44,12 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new SubsPleaseRequestGenerator { Settings = Settings, Capabilities = Capabilities };
return new SubsPleaseRequestGenerator(Settings);
}
public override IParseIndexerResponse GetParser()
{
return new SubsPleaseParser(Settings, Capabilities.Categories);
return new SubsPleaseParser(Settings);
}
private IndexerCapabilities SetCapabilities()
@@ -74,12 +75,16 @@ namespace NzbDrone.Core.Indexers.Definitions
public class SubsPleaseRequestGenerator : IIndexerRequestGenerator
{
public NoAuthTorrentBaseSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
private readonly NoAuthTorrentBaseSettings _settings;
public SubsPleaseRequestGenerator(NoAuthTorrentBaseSettings settings)
{
_settings = settings;
}
private IEnumerable<IndexerRequest> GetSearchRequests(string term)
{
var searchUrl = $"{Settings.BaseUrl.TrimEnd('/')}/api/?";
var searchUrl = $"{_settings.BaseUrl.TrimEnd('/')}/api/?";
var searchTerm = Regex.Replace(term, "\\[?SubsPlease\\]?\\s*", string.Empty, RegexOptions.IgnoreCase).Trim();
@@ -104,7 +109,7 @@ namespace NzbDrone.Core.Indexers.Definitions
private IEnumerable<IndexerRequest> GetRssRequest()
{
var searchUrl = $"{Settings.BaseUrl.TrimEnd('/')}/api/?";
var searchUrl = $"{_settings.BaseUrl.TrimEnd('/')}/api/?";
var queryParameters = new NameValueCollection
{
@@ -119,16 +124,12 @@ namespace NzbDrone.Core.Indexers.Definitions
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
return pageableRequests;
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
return pageableRequests;
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
@@ -166,13 +167,13 @@ namespace NzbDrone.Core.Indexers.Definitions
public class SubsPleaseParser : IParseIndexerResponse
{
private readonly NoAuthTorrentBaseSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private static readonly Regex RegexSize = new (@"\&xl=(?<size>\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public SubsPleaseParser(NoAuthTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
private readonly NoAuthTorrentBaseSettings _settings;
public SubsPleaseParser(NoAuthTorrentBaseSettings settings)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -216,28 +217,11 @@ namespace NzbDrone.Core.Indexers.Definitions
}
// Ex: [SubsPlease] Shingeki no Kyojin (The Final Season) - 64 (1080p)
release.Title += $"[SubsPlease] {value.Show} - {value.Episode} ({d.Res}p)";
release.Title += $"[SubsPlease] {value.Show} - {value.Episode} ({d.Resolution}p)";
release.MagnetUrl = d.Magnet;
release.DownloadUrl = null;
release.Guid = d.Magnet;
// The API doesn't tell us file size, so give an estimate based on resolution
if (string.Equals(d.Res, "1080"))
{
release.Size = 1395864371; // 1.3GB
}
else if (string.Equals(d.Res, "720"))
{
release.Size = 734003200; // 700MB
}
else if (string.Equals(d.Res, "480"))
{
release.Size = 367001600; // 350MB
}
else
{
release.Size = 1073741824; // 1GB
}
release.Size = GetReleaseSize(d);
torrentInfos.Add(release);
}
@@ -246,6 +230,30 @@ namespace NzbDrone.Core.Indexers.Definitions
return torrentInfos.ToArray();
}
private static long GetReleaseSize(SubPleaseDownloadInfo info)
{
if (info.Magnet.IsNotNullOrWhiteSpace())
{
var sizeMatch = RegexSize.Match(info.Magnet);
if (sizeMatch.Success &&
long.TryParse(sizeMatch.Groups["size"].Value, out var releaseSize)
&& releaseSize > 0)
{
return releaseSize;
}
}
// The API doesn't tell us file size, so give an estimate based on resolution
return info.Resolution switch
{
"1080" => 1.3.Gigabytes(),
"720" => 700.Megabytes(),
"480" => 350.Megabytes(),
_ => 1.Gigabytes()
};
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
@@ -265,7 +273,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public class SubPleaseDownloadInfo
{
public string Res { get; set; }
[JsonProperty("res")]
public string Resolution { get; set; }
public string Magnet { get; set; }
}
}

View File

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

View File

@@ -336,7 +336,7 @@ namespace NzbDrone.Core.Indexers.Definitions
ReleaseTypes = new List<int>();
}
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "Site API Key")]
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "IndexerTorrentSyndikatSettingsApiKeyHelpText")]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Products Only", Type = FieldType.Checkbox, HelpText = "Limit search to torrents linked to a product")]

View File

@@ -19,7 +19,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public class TorrentsCSV : TorrentIndexerBase<NoAuthTorrentBaseSettings>
{
public override string Name => "TorrentsCSV";
public override string[] IndexerUrls => new[] { "https://torrents-csv.ml/" };
public override string[] IndexerUrls => new[] { "https://torrents-csv.com/" };
public override string[] LegacyUrls => new[] { "https://torrents-csv.ml/" };
public override string Language => "en-US";
public override string Description => "Torrents.csv is a self-hostable open source torrent search engine and database";
public override Encoding Encoding => Encoding.UTF8;

View File

@@ -277,6 +277,18 @@ namespace NzbDrone.Core.Indexers.Torznab
flags.Add(IndexerFlag.FreeLeech);
}
var tags = TryGetMultipleTorznabAttributes(item, "tag");
if (tags.Any(t => t.EqualsIgnoreCase("internal")))
{
flags.Add(IndexerFlag.Internal);
}
if (tags.Any(t => t.EqualsIgnoreCase("scene")))
{
flags.Add(IndexerFlag.Scene);
}
return flags;
}

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Indexers.Definitions.UNIT3D
{
private static readonly Unit3dSettingsValidator Validator = new ();
[FieldDefinition(2, Label = "API Key", HelpText = "Site API Key generated in My Security", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(2, Label = "ApiKey", HelpText = "Site API Key generated in My Security", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
public override NzbDroneValidationResult Validate()

View File

@@ -21,13 +21,13 @@ namespace NzbDrone.Core.Indexers
public class IndexerBaseSettings
{
[FieldDefinition(1, Type = FieldType.Number, Label = "Query Limit", HelpText = "The number of max queries as specified by the respective unit that Prowlarr will allow to the site", Advanced = true)]
[FieldDefinition(1, Type = FieldType.Number, Label = "IndexerSettingsQueryLimit", HelpText = "IndexerSettingsQueryLimitHelpText", Advanced = true)]
public int? QueryLimit { get; set; }
[FieldDefinition(2, Type = FieldType.Number, Label = "Grab Limit", HelpText = "The number of max grabs as specified by the respective unit that Prowlarr will allow to the site", Advanced = true)]
[FieldDefinition(2, Type = FieldType.Number, Label = "IndexerSettingsGrabLimit", HelpText = "IndexerSettingsGrabLimitHelpText", Advanced = true)]
public int? GrabLimit { get; set; }
[FieldDefinition(3, Type = FieldType.Select, SelectOptions = typeof(IndexerLimitsUnit), Label = "Limits Unit", HelpText = "The unit of time for counting limits per indexer", Advanced = true)]
[FieldDefinition(3, Type = FieldType.Select, Label = "IndexerSettingsLimitsUnit", SelectOptions = typeof(IndexerLimitsUnit), HelpText = "IndexerSettingsLimitsUnitHelpText", Advanced = true)]
public int LimitsUnit { get; set; } = (int)IndexerLimitsUnit.Day;
}

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Http.Dispatchers;
using NzbDrone.Common.TPL;
@@ -34,56 +36,63 @@ namespace NzbDrone.Core.Indexers
public async Task<HttpResponse> ExecuteProxiedAsync(HttpRequest request, ProviderDefinition definition)
{
var selectedProxy = GetProxy(definition);
var selectedProxies = GetProxies(definition);
request = PreRequest(request, selectedProxy);
request = PreRequest(request, selectedProxies);
return PostResponse(await ExecuteAsync(request), selectedProxy);
return PostResponse(await ExecuteAsync(request), selectedProxies);
}
public HttpResponse ExecuteProxied(HttpRequest request, ProviderDefinition definition)
{
var selectedProxy = GetProxy(definition);
var selectedProxies = GetProxies(definition);
request = PreRequest(request, selectedProxy);
request = PreRequest(request, selectedProxies);
return PostResponse(Execute(request), selectedProxy);
return PostResponse(Execute(request), selectedProxies);
}
private IIndexerProxy GetProxy(ProviderDefinition definition)
private IList<IIndexerProxy> GetProxies(ProviderDefinition definition)
{
// Skip DB call if no tags on the indexers
if (definition is { Id: > 0 } && definition.Tags.Count == 0)
{
return null;
return Array.Empty<IIndexerProxy>();
}
var proxies = _indexerProxyFactory.GetAvailableProviders();
var selectedProxy = proxies.FirstOrDefault(proxy => definition.Tags.Intersect(proxy.Definition.Tags).Any());
if (selectedProxy == null && definition is not { Id: not 0 })
var selectedProxies = proxies
.Where(proxy => definition.Tags.Intersect(proxy.Definition.Tags).Any())
.GroupBy(p => p is FlareSolverr)
.Select(g => g.First())
.OrderBy(p => p is FlareSolverr)
.ToList();
if (!selectedProxies.Any() && definition is not { Id: not 0 })
{
selectedProxy = proxies.FirstOrDefault(p => p is FlareSolverr);
selectedProxies = new List<IIndexerProxy>();
selectedProxies.AddIfNotNull(proxies.Find(p => p is FlareSolverr));
}
return selectedProxy;
return selectedProxies;
}
private HttpRequest PreRequest(HttpRequest request, IIndexerProxy selectedProxy)
private HttpRequest PreRequest(HttpRequest request, IList<IIndexerProxy> selectedProxies)
{
if (selectedProxy != null)
if (selectedProxies != null && selectedProxies.Any())
{
request = selectedProxy.PreRequest(request);
request = selectedProxies.Aggregate(request, (current, selectedProxy) => selectedProxy.PreRequest(current));
}
return request;
}
private HttpResponse PostResponse(HttpResponse response, IIndexerProxy selectedProxy)
private HttpResponse PostResponse(HttpResponse response, IList<IIndexerProxy> selectedProxies)
{
if (selectedProxy != null)
if (selectedProxies != null && selectedProxies.Any())
{
response = selectedProxy.PostResponse(response);
response = selectedProxies.Aggregate(response, (current, selectedProxy) => selectedProxy.PostResponse(current));
}
return response;

View File

@@ -52,16 +52,19 @@ namespace NzbDrone.Core.Indexers
public class IndexerTorrentBaseSettings
{
[FieldDefinition(1, Type = FieldType.Number, Label = "Apps Minimum Seeders", HelpText = "Minimum seeders required by the Applications for the indexer to grab, empty is Sync profile's default", Advanced = true)]
[FieldDefinition(1, Type = FieldType.Number, Label = "IndexerSettingsAppsMinimumSeeders", HelpText = "IndexerSettingsAppsMinimumSeedersHelpText", Advanced = true)]
public int? AppMinimumSeeders { get; set; }
[FieldDefinition(2, Type = FieldType.Textbox, Label = "Seed Ratio", HelpText = "The ratio a torrent should reach before stopping, empty is app's default", Advanced = true)]
[FieldDefinition(2, Type = FieldType.Textbox, Label = "IndexerSettingsSeedRatio", HelpText = "IndexerSettingsSeedRatioHelpText")]
public double? SeedRatio { get; set; }
[FieldDefinition(3, Type = FieldType.Number, Label = "Seed Time", HelpText = "The time a torrent should be seeded before stopping, empty is app's default", Unit = "minutes", Advanced = true)]
[FieldDefinition(3, Type = FieldType.Number, Label = "IndexerSettingsSeedTime", Unit = "minutes", HelpText = "IndexerSettingsSeedTimeHelpText", Advanced = true)]
public int? SeedTime { get; set; }
[FieldDefinition(4, Type = FieldType.Number, Label = "Pack Seed Time", HelpText = "The time a pack (season or discography) torrent should be seeded before stopping, empty is app's default", Unit = "minutes", Advanced = true)]
[FieldDefinition(4, Type = FieldType.Number, Label = "IndexerSettingsPackSeedTime", HelpText = "IndexerSettingsPackSeedTimeIndexerHelpText", Unit = "minutes", Advanced = true)]
public int? PackSeedTime { get; set; }
[FieldDefinition(5, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
}
}

View File

@@ -10,26 +10,26 @@ namespace NzbDrone.Core.Indexers
{
private static readonly List<string> Trackers = new ()
{
"udp://tracker.opentrackr.org:1337/announce",
"https://tracker2.ctix.cn:443/announce",
"https://tracker1.520.jp:443/announce",
"http://tracker.opentrackr.org:1337/announce",
"udp://tracker.auctor.tv:6969/announce",
"udp://opentracker.i2p.rocks:6969/announce",
"udp://open.tracker.cl:1337/announce",
"https://opentracker.i2p.rocks:443/announce",
"udp://open.demonii.com:1337/announce",
"udp://tracker.openbittorrent.com:6969/announce",
"http://tracker.openbittorrent.com:80/announce",
"udp://open.stealth.si:80/announce",
"udp://exodus.desync.com:6969/announce",
"udp://tracker.torrent.eu.org:451/announce",
"udp://tracker1.bt.moack.co.kr:80/announce",
"udp://tracker-udp.gbitt.info:80/announce",
"udp://tracker.moeking.me:6969/announce",
"udp://explodie.org:6969/announce",
"https://tracker.gbitt.info:443/announce",
"http://tracker.gbitt.info:80/announce",
"http://bt.endpot.com:80/announce",
"udp://exodus.desync.com:6969/announce",
"udp://uploads.gamecoast.net:6969/announce",
"udp://tracker1.bt.moack.co.kr:80/announce",
"udp://tracker.tiny-vps.com:6969/announce",
"udp://tracker.auctor.tv:6969/announce",
"udp://tk1.trackerservers.com:8080/announce"
"udp://tracker.theoks.net:6969/announce",
"udp://tracker.skyts.net:6969/announce",
"udp://tracker-udp.gbitt.info:80/announce",
"udp://open.tracker.ink:6969/announce",
"udp://movies.zsw.ca:6969/announce"
};
public static string BuildPublicMagnetLink(string infoHash, string releaseTitle)

View File

@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Indexers.Settings
Cookie = "";
}
[FieldDefinition(2, Label = "Cookie", HelpText = "Site Cookie", HelpLink = "https://wiki.servarr.com/useful-tools#finding-cookies", Privacy = PrivacyLevel.Password)]
[FieldDefinition(2, Label = "IndexerSettingsCookie", HelpText = "IndexerSettingsCookieHelpText", HelpLink = "https://wiki.servarr.com/useful-tools#finding-cookies", Privacy = PrivacyLevel.Password)]
public string Cookie { get; set; }
public override NzbDroneValidationResult Validate()

View File

@@ -18,7 +18,7 @@ namespace NzbDrone.Core.Indexers.Settings
{
private static readonly NoAuthSettingsValidator<NoAuthTorrentBaseSettings> Validator = new ();
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
[FieldDefinition(1, Label = "IndexerSettingsBaseUrl", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "IndexerSettingsBaseUrlHelpText")]
public string BaseUrl { get; set; }
[FieldDefinition(20)]

View File

@@ -82,7 +82,7 @@
"ConnectSettings": "ربط الإعدادات",
"CouldNotConnectSignalR": "تعذر الاتصال بـ SignalR ، لن يتم تحديث واجهة المستخدم",
"Dates": "تواريخ",
"DBMigration": "ترحيل DB",
"DatabaseMigration": "ترحيل DB",
"Delete": "حذف",
"Details": "تفاصيل",
"Donations": "التبرعات",
@@ -150,7 +150,7 @@
"RemovingTag": "إزالة العلامة",
"Reset": "إعادة تعيين",
"ResetAPIKey": "إعادة تعيين مفتاح API",
"RSSIsNotSupportedWithThisIndexer": "لا يتم دعم RSS مع هذا المفهرس",
"RssIsNotSupportedWithThisIndexer": "لا يتم دعم RSS مع هذا المفهرس",
"Save": "حفظ",
"ScriptPath": "مسار البرنامج النصي",
"SettingsEnableColorImpairedMode": "تفعيل وضع ضعف الألوان",
@@ -251,13 +251,13 @@
"NoChanges": "لا تغيرات",
"NoLeaveIt": "لا ، اتركها",
"PendingChangesDiscardChanges": "تجاهل التغييرات واترك",
"RSS": "RSS",
"Rss": "RSS",
"System": "النظام",
"TagIsNotUsedAndCanBeDeleted": "العلامة غير مستخدمة ويمكن حذفها",
"Tags": "العلامات",
"TagsHelpText": "ينطبق على الأفلام التي تحتوي على علامة مطابقة واحدة على الأقل",
"UISettings": "إعدادات واجهة المستخدم",
"UnableToLoadDownloadClients": "تعذر تحميل عملاء التنزيل",
"DownloadClientsLoadError": "تعذر تحميل عملاء التنزيل",
"UnableToLoadTags": "تعذر تحميل العلامات",
"UnableToLoadUISettings": "تعذر تحميل إعدادات واجهة المستخدم",
"UpdateCheckStartupTranslocationMessage": "لا يمكن تثبيت التحديث لأن مجلد بدء التشغيل \"{0}\" موجود في مجلد App Translocation.",

View File

@@ -35,7 +35,7 @@
"CustomFilters": "Персонализирани филтри",
"Date": "Дата",
"Dates": "Дати",
"DBMigration": "DB миграция",
"DatabaseMigration": "DB миграция",
"Delete": "Изтрий",
"DeleteDownloadClient": "Изтриване на клиент за изтегляне",
"DeleteDownloadClientMessageText": "Наистина ли искате да изтриете клиента за изтегляне '{0}'?",
@@ -170,7 +170,7 @@
"RemovingTag": "Премахване на етикет",
"Reset": "Нулиране",
"Retention": "Задържане",
"RSS": "RSS",
"Rss": "RSS",
"Warn": "Предупреждавайте",
"NoTagsHaveBeenAddedYet": "Все още не са добавени етикети",
"NotificationTriggers": "Задействания за уведомяване",
@@ -188,7 +188,7 @@
"Priority": "Приоритет",
"SettingsLongDateFormat": "Формат с дълга дата",
"SettingsShowRelativeDates": "Показване на относителни дати",
"UnableToLoadDownloadClients": "Клиентите за изтегляне не могат да се заредят",
"DownloadClientsLoadError": "Клиентите за изтегляне не могат да се заредят",
"Logging": "Регистрация",
"Exception": "Изключение",
"MovieIndexScrollBottom": "Индекс на филма: Превъртане отдолу",
@@ -296,7 +296,7 @@
"Restart": "Рестартирам",
"RestartNow": "Рестартирай сега",
"Restore": "Възстанови",
"RSSIsNotSupportedWithThisIndexer": "RSS не се поддържа с този индексатор",
"RssIsNotSupportedWithThisIndexer": "RSS не се поддържа с този индексатор",
"Wiki": "Wiki",
"PageSizeHelpText": "Брой елементи за показване на всяка страница",
"Password": "Парола",

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