mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-04-17 21:44:48 -04:00
Compare commits
74 Commits
v1.12.0.41
...
v1.14.0.42
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d35f1dcc7 | ||
|
|
858f16195e | ||
|
|
a1a5dd574e | ||
|
|
a5ecc2dc9f | ||
|
|
7d46660583 | ||
|
|
22cbf40e3c | ||
|
|
25821c758f | ||
|
|
6153737a78 | ||
|
|
07adb45d63 | ||
|
|
02bc40b9b6 | ||
|
|
83e7e30e4f | ||
|
|
ae870fd46a | ||
|
|
33b7ba8725 | ||
|
|
dd2567a85f | ||
|
|
264ff8f885 | ||
|
|
629c6a8891 | ||
|
|
0ce2f96789 | ||
|
|
cd7d1571db | ||
|
|
4558f55282 | ||
|
|
21589fda57 | ||
|
|
3496263cd2 | ||
|
|
1bb1ec6106 | ||
|
|
2bfb838933 | ||
|
|
9eb291f578 | ||
|
|
8cf892124c | ||
|
|
47fb886930 | ||
|
|
5034a211cb | ||
|
|
ed1364b6ff | ||
|
|
71e18b616d | ||
|
|
f7bf21df68 | ||
|
|
d764e3405d | ||
|
|
16baceb784 | ||
|
|
5d2b80d15a | ||
|
|
a20a81f424 | ||
|
|
ebb66e9086 | ||
|
|
cb8797693e | ||
|
|
255c6335ae | ||
|
|
155cd53dcd | ||
|
|
ae70a96c10 | ||
|
|
16c0daf090 | ||
|
|
34c78c5a9d | ||
|
|
dd5b108ffd | ||
|
|
0b83986255 | ||
|
|
2bd25fb6f3 | ||
|
|
0f5eb5d3a3 | ||
|
|
c9434c61e3 | ||
|
|
ee969b7a06 | ||
|
|
cfdf88a1e2 | ||
|
|
70a5dae293 | ||
|
|
b53f8d4552 | ||
|
|
9668e91b21 | ||
|
|
49857693c0 | ||
|
|
850315ad1c | ||
|
|
86124d4319 | ||
|
|
4f28d583d7 | ||
|
|
01f3930211 | ||
|
|
7c7114c87a | ||
|
|
ef8e6d774b | ||
|
|
2960fc37d9 | ||
|
|
8bddf753bb | ||
|
|
cff24b3fd4 | ||
|
|
031d81330d | ||
|
|
6201b42fbd | ||
|
|
7022054dd7 | ||
|
|
c9b663247c | ||
|
|
0b0a0cfa5b | ||
|
|
3c0fea8b7c | ||
|
|
ac97952fd7 | ||
|
|
c3e40c0564 | ||
|
|
ce615a77c2 | ||
|
|
0f6dfe389c | ||
|
|
25d94a9286 | ||
|
|
52a690b41a | ||
|
|
56c8c3d6c6 |
2
.github/workflows/label-actions.yml
vendored
2
.github/workflows/label-actions.yml
vendored
@@ -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'
|
||||
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '1.12.0'
|
||||
majorVersion: '1.14.0'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
@@ -17,7 +17,7 @@ variables:
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '6.0.417'
|
||||
nodeVersion: '16.X'
|
||||
innoVersion: '6.2.0'
|
||||
innoVersion: '6.2.2'
|
||||
windowsImage: 'windows-2022'
|
||||
linuxImage: 'ubuntu-20.04'
|
||||
macImage: 'macOS-11'
|
||||
@@ -1206,6 +1206,7 @@ stages:
|
||||
- stage: Report_Out
|
||||
dependsOn:
|
||||
- Analyze
|
||||
- Installer
|
||||
- Unit_Test
|
||||
- Integration
|
||||
- Automation
|
||||
|
||||
2
build.sh
2
build.sh
@@ -254,7 +254,7 @@ InstallInno()
|
||||
ProgressStart "Installing portable Inno Setup"
|
||||
|
||||
rm -rf _inno
|
||||
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.0}.exe"
|
||||
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.2}.exe"
|
||||
mkdir _inno
|
||||
./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno
|
||||
rm innosetup.exe
|
||||
|
||||
@@ -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 }],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import SortDirection from 'Helpers/Props/SortDirection';
|
||||
import { FilterBuilderProp } from './AppState';
|
||||
|
||||
export interface Error {
|
||||
responseJSON: {
|
||||
@@ -20,6 +21,10 @@ export interface PagedAppSectionState {
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface AppSectionFilterState<T> {
|
||||
filterBuilderProps: FilterBuilderProp<T>[];
|
||||
}
|
||||
|
||||
export interface AppSectionSchemaState<T> {
|
||||
isSchemaFetching: boolean;
|
||||
isSchemaPopulated: boolean;
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import AppSectionState from 'App/State/AppSectionState';
|
||||
import AppSectionState, {
|
||||
AppSectionFilterState,
|
||||
} from 'App/State/AppSectionState';
|
||||
import Column from 'Components/Table/Column';
|
||||
import History from 'typings/History';
|
||||
|
||||
interface HistoryAppState extends AppSectionState<History> {
|
||||
interface HistoryAppState
|
||||
extends AppSectionState<History>,
|
||||
AppSectionFilterState<History> {
|
||||
pageSize: number;
|
||||
columns: Column[];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -5,8 +5,10 @@ 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';
|
||||
import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector';
|
||||
import PrivacyFilterBuilderRowValue from './PrivacyFilterBuilderRowValue';
|
||||
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
|
||||
@@ -55,9 +57,15 @@ function getRowValueConnector(selectedFilterBuilderProp) {
|
||||
case filterBuilderValueTypes.BOOL:
|
||||
return BoolFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.CATEGORY:
|
||||
return CategoryFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.DATE:
|
||||
return DateFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.HISTORY_EVENT_TYPE:
|
||||
return HistoryEventTypeFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.INDEXER:
|
||||
return IndexerFilterBuilderRowValueConnector;
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { FilterBuilderProp } from 'App/State/AppState';
|
||||
|
||||
interface FilterBuilderRowOnChangeProps {
|
||||
name: string;
|
||||
value: unknown[];
|
||||
}
|
||||
|
||||
interface FilterBuilderRowValueProps {
|
||||
filterType?: string;
|
||||
filterValue: string | number | object | string[] | number[] | object[];
|
||||
selectedFilterBuilderProp: FilterBuilderProp<unknown>;
|
||||
sectionItem: unknown[];
|
||||
onChange: (payload: FilterBuilderRowOnChangeProps) => void;
|
||||
}
|
||||
|
||||
export default FilterBuilderRowValueProps;
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
|
||||
|
||||
const EVENT_TYPE_OPTIONS = [
|
||||
{
|
||||
id: 1,
|
||||
get name() {
|
||||
return translate('Grabbed');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
get name() {
|
||||
return translate('IndexerRss');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
get name() {
|
||||
return translate('IndexerQuery');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
get name() {
|
||||
return translate('IndexerAuth');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function HistoryEventTypeFilterBuilderRowValue(
|
||||
props: FilterBuilderRowValueProps
|
||||
) {
|
||||
return <FilterBuilderRowValue {...props} tagList={EVENT_TYPE_OPTIONS} />;
|
||||
}
|
||||
|
||||
export default HistoryEventTypeFilterBuilderRowValue;
|
||||
@@ -30,22 +30,24 @@ function CustomFiltersModalContent(props) {
|
||||
|
||||
<ModalBody>
|
||||
{
|
||||
customFilters.map((customFilter) => {
|
||||
return (
|
||||
<CustomFilter
|
||||
key={customFilter.id}
|
||||
id={customFilter.id}
|
||||
label={customFilter.label}
|
||||
filters={customFilter.filters}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
isDeleting={isDeleting}
|
||||
deleteError={deleteError}
|
||||
dispatchSetFilter={dispatchSetFilter}
|
||||
dispatchDeleteCustomFilter={dispatchDeleteCustomFilter}
|
||||
onEditPress={onEditCustomFilter}
|
||||
/>
|
||||
);
|
||||
})
|
||||
customFilters
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
.map((customFilter) => {
|
||||
return (
|
||||
<CustomFilter
|
||||
key={customFilter.id}
|
||||
id={customFilter.id}
|
||||
label={customFilter.label}
|
||||
filters={customFilter.filters}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
isDeleting={isDeleting}
|
||||
deleteError={deleteError}
|
||||
dispatchSetFilter={dispatchSetFilter}
|
||||
dispatchDeleteCustomFilter={dispatchDeleteCustomFilter}
|
||||
onEditPress={onEditCustomFilter}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
<div className={styles.addButtonContainer}>
|
||||
|
||||
@@ -24,7 +24,8 @@ function createMapStateToProps() {
|
||||
.sort(sortByName)
|
||||
.map((downloadClient) => ({
|
||||
key: downloadClient.id,
|
||||
value: downloadClient.name
|
||||
value: downloadClient.name,
|
||||
hint: `(${downloadClient.id})`
|
||||
}));
|
||||
|
||||
if (includeAny) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -40,18 +40,26 @@ class FilterMenuContent extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
customFilters.map((filter) => {
|
||||
return (
|
||||
<FilterMenuItem
|
||||
key={filter.id}
|
||||
filterKey={filter.id}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
onPress={onFilterSelect}
|
||||
>
|
||||
{filter.label}
|
||||
</FilterMenuItem>
|
||||
);
|
||||
})
|
||||
customFilters.length > 0 ?
|
||||
<MenuItemSeparator /> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
customFilters
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
.map((filter) => {
|
||||
return (
|
||||
<FilterMenuItem
|
||||
key={filter.id}
|
||||
filterKey={filter.id}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
onPress={onFilterSelect}
|
||||
>
|
||||
{filter.label}
|
||||
</FilterMenuItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
"start_url": "../../../../",
|
||||
"theme_color": "#3a3f51",
|
||||
"background_color": "#3a3f51",
|
||||
"display": "minimal-ui"
|
||||
"display": "standalone"
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ export const BOOL = 'bool';
|
||||
export const BYTES = 'bytes';
|
||||
export const DATE = 'date';
|
||||
export const DEFAULT = 'default';
|
||||
export const HISTORY_EVENT_TYPE = 'historyEventType';
|
||||
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';
|
||||
|
||||
@@ -15,6 +15,7 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import HistoryFilterModal from './HistoryFilterModal';
|
||||
import HistoryOptionsConnector from './HistoryOptionsConnector';
|
||||
import HistoryRowConnector from './HistoryRowConnector';
|
||||
|
||||
@@ -63,6 +64,7 @@ class History extends Component {
|
||||
columns,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
totalRecords,
|
||||
onFilterSelect,
|
||||
onFirstPagePress,
|
||||
@@ -108,7 +110,8 @@ class History extends Component {
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={[]}
|
||||
customFilters={customFilters}
|
||||
filterModalConnectorComponent={HistoryFilterModal}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
@@ -193,8 +196,9 @@ History.propTypes = {
|
||||
indexersError: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
selectedFilterKey: PropTypes.string.isRequired,
|
||||
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
totalRecords: PropTypes.number,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
onFirstPagePress: PropTypes.func.isRequired,
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as commandNames from 'Commands/commandNames';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import * as historyActions from 'Store/Actions/historyActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import History from './History';
|
||||
@@ -14,13 +15,15 @@ function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.history,
|
||||
(state) => state.indexers,
|
||||
createCustomFiltersSelector('history'),
|
||||
createCommandExecutingSelector(commandNames.CLEAR_HISTORY),
|
||||
(history, indexers, isHistoryClearing) => {
|
||||
(history, indexers, customFilters, isHistoryClearing) => {
|
||||
return {
|
||||
isIndexersFetching: indexers.isFetching,
|
||||
isIndexersPopulated: indexers.isPopulated,
|
||||
indexersError: indexers.error,
|
||||
isHistoryClearing,
|
||||
customFilters,
|
||||
...history
|
||||
};
|
||||
}
|
||||
|
||||
54
frontend/src/History/HistoryFilterModal.tsx
Normal file
54
frontend/src/History/HistoryFilterModal.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import FilterModal from 'Components/Filter/FilterModal';
|
||||
import { setHistoryFilter } from 'Store/Actions/historyActions';
|
||||
|
||||
function createHistorySelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.history.items,
|
||||
(queueItems) => {
|
||||
return queueItems;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createFilterBuilderPropsSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.history.filterBuilderProps,
|
||||
(filterBuilderProps) => {
|
||||
return filterBuilderProps;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
interface HistoryFilterModalProps {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export default function HistoryFilterModal(props: HistoryFilterModalProps) {
|
||||
const sectionItems = useSelector(createHistorySelector());
|
||||
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
|
||||
const customFilterType = 'history';
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const dispatchSetFilter = useCallback(
|
||||
(payload: unknown) => {
|
||||
dispatch(setHistoryFilter(payload));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<FilterModal
|
||||
// TODO: Don't spread all the props
|
||||
{...props}
|
||||
sectionItems={sectionItems}
|
||||
filterBuilderProps={filterBuilderProps}
|
||||
customFilterType={customFilterType}
|
||||
dispatchSetFilter={dispatchSetFilter}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
flex: 0 0 60px;
|
||||
}
|
||||
|
||||
.id {
|
||||
composes: cell;
|
||||
|
||||
flex: 0 0 60px;
|
||||
}
|
||||
|
||||
.sortName {
|
||||
composes: cell;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ interface CssExports {
|
||||
'cell': string;
|
||||
'checkInput': string;
|
||||
'externalLink': string;
|
||||
'id': string;
|
||||
'minimumSeeders': string;
|
||||
'packSeedTime': string;
|
||||
'priority': string;
|
||||
|
||||
@@ -34,7 +34,7 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
|
||||
const { indexerId, columns, isSelectMode, onCloneIndexerPress } = props;
|
||||
|
||||
const { indexer, appProfile, status, longDateFormat, timeFormat } =
|
||||
useSelector(createIndexerIndexItemSelector(props.indexerId));
|
||||
useSelector(createIndexerIndexItemSelector(indexerId));
|
||||
|
||||
const {
|
||||
id,
|
||||
@@ -148,12 +148,24 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'id') {
|
||||
return (
|
||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||
<IndexerTitleLink
|
||||
indexerId={indexerId}
|
||||
title={`${indexerId}`}
|
||||
onCloneIndexerPress={onCloneIndexerPress}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'sortName') {
|
||||
return (
|
||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||
<IndexerTitleLink
|
||||
indexerId={indexerId}
|
||||
indexerName={indexerName}
|
||||
title={indexerName}
|
||||
onCloneIndexerPress={onCloneIndexerPress}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
flex: 0 0 60px;
|
||||
}
|
||||
|
||||
.id {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 60px;
|
||||
}
|
||||
|
||||
.sortName {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ interface CssExports {
|
||||
'added': string;
|
||||
'appProfileId': string;
|
||||
'capabilities': string;
|
||||
'id': string;
|
||||
'minimumSeeders': string;
|
||||
'packSeedTime': string;
|
||||
'priority': string;
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import Link from 'Components/Link/Link';
|
||||
import IndexerInfoModal from './Info/IndexerInfoModal';
|
||||
import styles from './IndexerTitleLink.css';
|
||||
|
||||
interface IndexerTitleLinkProps {
|
||||
indexerName: string;
|
||||
indexerId: number;
|
||||
title: string;
|
||||
onCloneIndexerPress(id: number): void;
|
||||
}
|
||||
|
||||
function IndexerTitleLink(props: IndexerTitleLinkProps) {
|
||||
const { indexerName, indexerId, onCloneIndexerPress } = props;
|
||||
const { title, indexerId, onCloneIndexerPress } = props;
|
||||
|
||||
const [isIndexerInfoModalOpen, setIsIndexerInfoModalOpen] = useState(false);
|
||||
|
||||
@@ -26,7 +25,7 @@ function IndexerTitleLink(props: IndexerTitleLinkProps) {
|
||||
return (
|
||||
<div>
|
||||
<Link className={styles.link} onPress={onIndexerInfoPress}>
|
||||
{indexerName}
|
||||
{title}
|
||||
</Link>
|
||||
|
||||
<IndexerInfoModal
|
||||
@@ -39,8 +38,4 @@ function IndexerTitleLink(props: IndexerTitleLinkProps) {
|
||||
);
|
||||
}
|
||||
|
||||
IndexerTitleLink.propTypes = {
|
||||
indexerName: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default IndexerTitleLink;
|
||||
|
||||
@@ -12,8 +12,9 @@ import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import { align, kinds } from 'Helpers/Props';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import {
|
||||
fetchIndexerStats,
|
||||
setIndexerStatsFilter,
|
||||
@@ -194,6 +195,10 @@ function IndexerStats() {
|
||||
dispatch(fetchIndexerStats());
|
||||
}, [dispatch]);
|
||||
|
||||
const onRefreshPress = useCallback(() => {
|
||||
dispatch(fetchIndexerStats());
|
||||
}, [dispatch]);
|
||||
|
||||
const onFilterSelect = useCallback(
|
||||
(value: string) => {
|
||||
dispatch(setIndexerStatsFilter({ selectedFilterKey: value }));
|
||||
@@ -219,8 +224,17 @@ function IndexerStats() {
|
||||
}, 0) ?? 0;
|
||||
|
||||
return (
|
||||
<PageContent>
|
||||
<PageContent title={translate('Stats')}>
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label={translate('Refresh')}
|
||||
iconName={icons.REFRESH}
|
||||
isSpinning={isFetching}
|
||||
onPress={onRefreshPress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
<PageToolbarSection alignContent={align.RIGHT} collapseButtons={false}>
|
||||
<FilterMenu
|
||||
alignMenu={align.RIGHT}
|
||||
|
||||
@@ -285,7 +285,7 @@ class SearchIndex extends Component {
|
||||
const hasNoIndexer = !totalItems;
|
||||
|
||||
return (
|
||||
<PageContent>
|
||||
<PageContent title={translate('Search')}>
|
||||
<PageToolbar>
|
||||
<PageToolbarSection
|
||||
alignContent={align.RIGHT}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -84,7 +84,7 @@ class DownloadClientSettings extends Component {
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('ManageDownloadClients')}
|
||||
label={translate('ManageClients')}
|
||||
iconName={icons.MANAGE}
|
||||
onPress={this.onManageDownloadClientsPress}
|
||||
/>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -96,7 +96,7 @@ class AppProfile extends Component {
|
||||
kind={enableRss ? kinds.SUCCESS : kinds.DISABLED}
|
||||
outline={!enableRss}
|
||||
>
|
||||
{translate('RSS')}
|
||||
{translate('Rss')}
|
||||
</Label>
|
||||
}
|
||||
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -6,6 +6,8 @@ import getSectionState from 'Utilities/State/getSectionState';
|
||||
import { set, updateServerSideCollection } from '../baseActions';
|
||||
|
||||
function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter) {
|
||||
const [baseSection] = section.split('.');
|
||||
|
||||
return function(getState, payload, dispatch) {
|
||||
dispatch(set({ section, isFetching: true }));
|
||||
|
||||
@@ -25,10 +27,13 @@ function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter
|
||||
|
||||
const {
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters
|
||||
filters
|
||||
} = sectionState;
|
||||
|
||||
const customFilters = getState().customFilters.items.filter((customFilter) => {
|
||||
return customFilter.type === section || customFilter.type === baseSection;
|
||||
});
|
||||
|
||||
const selectedFilters = findSelectedFilters(selectedFilterKey, filters, customFilters);
|
||||
|
||||
selectedFilters.forEach((filter) => {
|
||||
@@ -37,7 +42,8 @@ function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url,
|
||||
data
|
||||
data,
|
||||
traditional: true
|
||||
}).request;
|
||||
|
||||
promise.done((response) => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import { filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
|
||||
@@ -159,6 +159,27 @@ export const defaultState = {
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
filterBuilderProps: [
|
||||
{
|
||||
name: 'eventType',
|
||||
label: () => translate('EventType'),
|
||||
type: filterBuilderTypes.EQUAL,
|
||||
valueType: filterBuilderValueTypes.HISTORY_EVENT_TYPE
|
||||
},
|
||||
{
|
||||
name: 'indexerIds',
|
||||
label: () => translate('Indexer'),
|
||||
type: filterBuilderTypes.EQUAL,
|
||||
valueType: filterBuilderValueTypes.INDEXER
|
||||
},
|
||||
{
|
||||
name: 'successful',
|
||||
label: () => translate('Successful'),
|
||||
type: filterBuilderTypes.EQUAL,
|
||||
valueType: filterBuilderValueTypes.BOOL
|
||||
}
|
||||
]
|
||||
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -37,12 +37,18 @@ export const defaultState = {
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
},
|
||||
{
|
||||
name: 'id',
|
||||
columnLabel: () => translate('IndexerId'),
|
||||
label: () => translate('Id'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'sortName',
|
||||
label: () => translate('IndexerName'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'protocol',
|
||||
@@ -180,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'),
|
||||
|
||||
@@ -74,8 +74,9 @@ export const defaultState = {
|
||||
valueType: filterBuilderValueTypes.TAG
|
||||
}
|
||||
],
|
||||
selectedFilterKey: 'all',
|
||||
customFilters: []
|
||||
|
||||
selectedFilterKey: 'all'
|
||||
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -92,6 +92,10 @@ namespace NzbDrone.Common.Http
|
||||
{
|
||||
data = new XElement("base64", Convert.ToBase64String(bytes));
|
||||
}
|
||||
else if (value is Dictionary<string, string> d)
|
||||
{
|
||||
data = new XElement("struct", d.Select(p => new XElement("member", new XElement("name", p.Key), new XElement("value", p.Value))));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Unhandled argument type {value.GetType().Name}");
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
|
||||
|
||||
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 2000 } })).Releases;
|
||||
|
||||
releases.Should().HaveCount(1463);
|
||||
releases.Should().HaveCount(1462);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
|
||||
var torrentInfo = releases.First() as TorrentInfo;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,11 +252,11 @@ namespace NzbDrone.Core.Applications
|
||||
if (webException.Message.Contains("502") || webException.Message.Contains("503") ||
|
||||
webException.Message.Contains("timed out"))
|
||||
{
|
||||
_logger.Warn("{0} server is currently unavailable. {1}", this, webException.Message);
|
||||
_logger.Warn(webException, "{0} server is currently unavailable. {1}", this, webException.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("{0} {1}", this, webException.Message);
|
||||
_logger.Warn(webException, "{0} {1}", this, webException.Message);
|
||||
}
|
||||
}
|
||||
catch (TooManyRequestsException ex)
|
||||
@@ -264,12 +264,12 @@ namespace NzbDrone.Core.Applications
|
||||
var minimumBackOff = ex.RetryAfter != TimeSpan.Zero ? ex.RetryAfter : TimeSpan.FromHours(1);
|
||||
_applicationStatusService.RecordFailure(application.Definition.Id, minimumBackOff);
|
||||
|
||||
_logger.Warn("API Request Limit reached for {0}", this);
|
||||
_logger.Warn(ex, "API Request Limit reached for {0}", this);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_applicationStatusService.RecordFailure(application.Definition.Id);
|
||||
_logger.Warn("{0} {1}", this, ex.Message);
|
||||
_logger.Warn(ex, "{0} {1}", this, ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -301,11 +301,11 @@ namespace NzbDrone.Core.Applications
|
||||
if (webException.Message.Contains("502") || webException.Message.Contains("503") ||
|
||||
webException.Message.Contains("timed out"))
|
||||
{
|
||||
_logger.Warn("{0} server is currently unavailable. {1}", this, webException.Message);
|
||||
_logger.Warn(webException, "{0} server is currently unavailable. {1}", this, webException.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("{0} {1}", this, webException.Message);
|
||||
_logger.Warn(webException, "{0} {1}", this, webException.Message);
|
||||
}
|
||||
}
|
||||
catch (TooManyRequestsException ex)
|
||||
@@ -313,12 +313,12 @@ namespace NzbDrone.Core.Applications
|
||||
var minimumBackOff = ex.RetryAfter != TimeSpan.Zero ? ex.RetryAfter : TimeSpan.FromHours(1);
|
||||
_applicationStatusService.RecordFailure(application.Definition.Id, minimumBackOff);
|
||||
|
||||
_logger.Warn("API Request Limit reached for {0}", this);
|
||||
_logger.Warn(ex, "API Request Limit reached for {0}", this);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_applicationStatusService.RecordFailure(application.Definition.Id);
|
||||
_logger.Warn("{0} {1}", this, ex.Message);
|
||||
_logger.Warn(ex, "{0} {1}", this, ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -227,7 +227,7 @@ namespace NzbDrone.Core.Configuration
|
||||
return urlBase;
|
||||
}
|
||||
|
||||
return "/" + urlBase.Trim('/').ToLower();
|
||||
return "/" + urlBase;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 =>
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Download.Extensions;
|
||||
|
||||
@@ -95,8 +96,14 @@ namespace NzbDrone.Core.Download.Clients.Aria2
|
||||
|
||||
public string AddUri(Aria2Settings settings, string magnet)
|
||||
{
|
||||
var response = ExecuteRequest(settings, "aria2.addUri", GetToken(settings), new List<string> { magnet });
|
||||
var options = new Dictionary<string, string>();
|
||||
|
||||
if (settings.Directory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
options.Add("dir", settings.Directory);
|
||||
}
|
||||
|
||||
var response = ExecuteRequest(settings, "aria2.addUri", GetToken(settings), new List<string> { magnet }, options);
|
||||
var gid = response.GetStringResponse();
|
||||
|
||||
return gid;
|
||||
@@ -104,8 +111,16 @@ namespace NzbDrone.Core.Download.Clients.Aria2
|
||||
|
||||
public string AddTorrent(Aria2Settings settings, byte[] torrent)
|
||||
{
|
||||
var response = ExecuteRequest(settings, "aria2.addTorrent", GetToken(settings), torrent);
|
||||
// Aria2's second parameter is an array of URIs and needs to be sent if options are provided, this satisfies that requirement.
|
||||
var emptyListOfUris = new List<string>();
|
||||
var options = new Dictionary<string, string>();
|
||||
|
||||
if (settings.Directory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
options.Add("dir", settings.Directory);
|
||||
}
|
||||
|
||||
var response = ExecuteRequest(settings, "aria2.addTorrent", GetToken(settings), torrent, emptyListOfUris, options);
|
||||
var gid = response.GetStringResponse();
|
||||
|
||||
return gid;
|
||||
|
||||
@@ -32,15 +32,18 @@ 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")]
|
||||
public string Directory { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -171,9 +171,7 @@ namespace NzbDrone.Core.Download
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
_logger.Error(ex, "Failed to parse magnetlink for release '{0}': '{1}'", release.Title, magnetUrl);
|
||||
|
||||
return null;
|
||||
throw new ReleaseDownloadException("Failed to parse magnetlink for release '{0}': '{1}'", ex, release.Title, magnetUrl);
|
||||
}
|
||||
|
||||
if (hash != null)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -38,3 +38,8 @@ public class FileListTorrent
|
||||
[JsonPropertyName("small_description")]
|
||||
public string SmallDescription { get; set; }
|
||||
}
|
||||
|
||||
public class FileListErrorResponse
|
||||
{
|
||||
public string Error { get; set; }
|
||||
}
|
||||
|
||||
@@ -28,9 +28,14 @@ public class FileListParser : IParseIndexerResponse
|
||||
throw new IndexerException(indexerResponse, "Unexpected response status {0} code from indexer request", indexerResponse.HttpResponse.StatusCode);
|
||||
}
|
||||
|
||||
if (indexerResponse.Content.StartsWith("{\"error\"") && STJson.TryDeserialize<FileListErrorResponse>(indexerResponse.Content, out var errorResponse))
|
||||
{
|
||||
throw new IndexerException(indexerResponse, "Unexpected response from indexer request: {0}", errorResponse.Error);
|
||||
}
|
||||
|
||||
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from indexer request, expected {HttpAccept.Json.Value}");
|
||||
throw new IndexerException(indexerResponse, "Unexpected response header {0} from indexer request, expected {1}", indexerResponse.HttpResponse.Headers.ContentType, HttpAccept.Json.Value);
|
||||
}
|
||||
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -394,7 +394,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
var torrents = groupTorrents
|
||||
.ToObject<Dictionary<int, GazelleGamesTorrent>>(JsonSerializer.Create(Json.GetSerializerSettings()))
|
||||
.Where(t => t.Value.TorrentType != "Link")
|
||||
.Where(t => t.Value.TorrentType.ToUpperInvariant() == "TORRENT")
|
||||
.ToList();
|
||||
|
||||
var categories = group.Value.Artists
|
||||
@@ -470,6 +470,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
flags.AddIfNotNull(torrent.Region);
|
||||
flags.AddIfNotNull(torrent.Miscellaneous);
|
||||
|
||||
if (torrent.Dupable == 1)
|
||||
{
|
||||
flags.Add("Trumpable");
|
||||
}
|
||||
|
||||
flags = flags.Where(x => x.IsNotNullOrWhiteSpace()).ToList();
|
||||
|
||||
if (flags.Any())
|
||||
@@ -477,6 +482,11 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
title += $" [{string.Join(" / ", flags)}]";
|
||||
}
|
||||
|
||||
if (torrent.GameDoxType.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
title += $" [{torrent.GameDoxType.Trim()}]";
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
@@ -525,10 +535,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; }
|
||||
@@ -570,6 +580,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public string ReleaseTitle { get; set; }
|
||||
public string Miscellaneous { get; set; }
|
||||
public int Scene { get; set; }
|
||||
public int Dupable { get; set; }
|
||||
public DateTime Time { get; set; }
|
||||
public string TorrentType { get; set; }
|
||||
public int FileCount { get; set; }
|
||||
@@ -580,6 +591,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public GazelleGamesFreeTorrent FreeTorrent { get; set; }
|
||||
public bool PersonalFL { get; set; }
|
||||
public bool LowSeedFL { get; set; }
|
||||
|
||||
[JsonProperty("GameDOXType")]
|
||||
public string GameDoxType { get; set; }
|
||||
}
|
||||
|
||||
public class GazelleGamesUserResponse
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
|
||||
public IEnumerable<int> Category { get; set; }
|
||||
public IEnumerable<int> Codec { get; set; }
|
||||
public IEnumerable<int> Medium { get; set; }
|
||||
public int? Origin { get; set; }
|
||||
public IEnumerable<int> Origin { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "imdb")]
|
||||
public ImdbInfo ImdbInfo { get; set; }
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
@@ -26,7 +27,7 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
|
||||
|
||||
if (imdbId == 0 && searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
query.Search = searchCriteria.SanitizedSearchTerm;
|
||||
query.Search = Regex.Replace(searchCriteria.SanitizedSearchTerm, "[\\W]+", " ").Trim();
|
||||
}
|
||||
|
||||
if (imdbId != 0)
|
||||
@@ -122,8 +123,20 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
|
||||
query.Username = Settings.Username;
|
||||
query.Passkey = Settings.ApiKey;
|
||||
|
||||
query.Codec = Settings.Codecs.ToArray();
|
||||
query.Medium = Settings.Mediums.ToArray();
|
||||
if (Settings.Codecs.Any())
|
||||
{
|
||||
query.Codec = Settings.Codecs.ToArray();
|
||||
}
|
||||
|
||||
if (Settings.Mediums.Any())
|
||||
{
|
||||
query.Medium = Settings.Mediums.ToArray();
|
||||
}
|
||||
|
||||
if (Settings.Origins.Any())
|
||||
{
|
||||
query.Origin = Settings.Origins.ToArray();
|
||||
}
|
||||
|
||||
if (searchCriteria.Categories?.Length > 0)
|
||||
{
|
||||
|
||||
@@ -24,26 +24,30 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
|
||||
{
|
||||
Codecs = Array.Empty<int>();
|
||||
Mediums = Array.Empty<int>();
|
||||
Origins = Array.Empty<int>();
|
||||
FreeleechOnly = false;
|
||||
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), Advanced = true, HelpText = "If unspecified, all options are used.")]
|
||||
[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), Advanced = true, HelpText = "If unspecified, all options are used.")]
|
||||
[FieldDefinition(5, Label = "IndexerHDBitsSettingsMediums", Type = FieldType.Select, SelectOptions = typeof(HdBitsMedium), HelpText = "IndexerHDBitsSettingsMediumsHelpText", Advanced = true)]
|
||||
public IEnumerable<int> Mediums { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Freeleech Only", Type = FieldType.Checkbox, Advanced = true, HelpText = "Show freeleech releases only")]
|
||||
[FieldDefinition(6, Label = "IndexerHDBitsSettingsOrigins", Type = FieldType.Select, SelectOptions = typeof(HdBitsOrigin), HelpText = "IndexerHDBitsSettingsOriginsHelpText", Advanced = true)]
|
||||
public IEnumerable<int> Origins { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "IndexerSettingsFreeleechOnly", Type = FieldType.Checkbox, HelpText = "IndexerHDBitsSettingsFreeleechOnlyHelpText", Advanced = true)]
|
||||
public bool FreeleechOnly { get; set; }
|
||||
|
||||
[FieldDefinition(7, 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()
|
||||
@@ -79,4 +83,12 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
|
||||
[FieldOption("WEB-DL")]
|
||||
WebDl = 6
|
||||
}
|
||||
|
||||
public enum HdBitsOrigin
|
||||
{
|
||||
[FieldOption("Undefined")]
|
||||
Undefined = 0,
|
||||
[FieldOption("Internal")]
|
||||
Internal = 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
@@ -10,10 +11,7 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
{
|
||||
public class TorznabSettingsValidator : AbstractValidator<TorznabSettings>
|
||||
{
|
||||
private static readonly string[] ApiKeyWhiteList =
|
||||
{
|
||||
"hd4free.xyz",
|
||||
};
|
||||
private static readonly string[] ApiKeyWhiteList = Array.Empty<string>();
|
||||
|
||||
private static bool ShouldHaveApiKey(TorznabSettings settings)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user