mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-03-05 13:40:08 -05:00
Compare commits
79 Commits
v1.11.4.41
...
v1.13.3.42
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
5cee8990b3 | ||
|
|
9679e88717 | ||
|
|
f4203993ba | ||
|
|
4be0715fe3 | ||
|
|
f9c9d4a0e0 | ||
|
|
bf47380f7b | ||
|
|
e27a46f578 | ||
|
|
af4e69f8fb | ||
|
|
1b2106d4f0 | ||
|
|
bdfbda3805 | ||
|
|
cb98b10468 | ||
|
|
ae1bc8366c | ||
|
|
67eeb4373c | ||
|
|
9d40a64be4 |
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '1.11.4'
|
||||
majorVersion: '1.13.3'
|
||||
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}>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
@@ -21,10 +22,10 @@ namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Definition = new IndexerDefinition()
|
||||
Subject.Definition = new IndexerDefinition
|
||||
{
|
||||
Name = "GazelleGames",
|
||||
Settings = new GazelleGamesSettings() { Apikey = "somekey" }
|
||||
Settings = new GazelleGamesSettings { Apikey = "somekey" }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,20 +38,20 @@ namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
|
||||
|
||||
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new int[] { 2000 } })).Releases;
|
||||
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 2000 } })).Releases;
|
||||
|
||||
releases.Should().HaveCount(1464);
|
||||
releases.Should().HaveCount(1462);
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
|
||||
var torrentInfo = releases.First() as TorrentInfo;
|
||||
|
||||
torrentInfo.Title.Should().Be("Microsoft_Flight_Simulator-HOODLUM");
|
||||
torrentInfo.Title.Should().Be("Microsoft_Flight_Simulator-HOODLUM (2020) [Windows / Multi-Language / Full ISO]");
|
||||
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
|
||||
torrentInfo.DownloadUrl.Should().Be("https://gazellegames.net/torrents.php?action=download&id=303216&authkey=prowlarr&torrent_pass=");
|
||||
torrentInfo.InfoUrl.Should().Be("https://gazellegames.net/torrents.php?id=84781&torrentid=303216");
|
||||
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
|
||||
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-07-25 6:39:11").ToUniversalTime());
|
||||
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-07-25 06:39:11", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal));
|
||||
torrentInfo.Size.Should().Be(80077617780);
|
||||
torrentInfo.InfoHash.Should().Be(null);
|
||||
torrentInfo.MagnetUrl.Should().Be(null);
|
||||
@@ -74,7 +75,7 @@ namespace NzbDrone.Core.Test.IndexerTests.GazelleGamesTests
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
|
||||
|
||||
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new int[] { 2000 } })).Releases;
|
||||
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 2000 } })).Releases;
|
||||
|
||||
releases.Should().HaveCount(0);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,23 @@ namespace NzbDrone.Core.Annotations
|
||||
public string Hint { get; set; }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
|
||||
public class FieldTokenAttribute : Attribute
|
||||
{
|
||||
public FieldTokenAttribute(TokenField field, string label = "", string token = "", object value = null)
|
||||
{
|
||||
Label = label;
|
||||
Field = field;
|
||||
Token = token;
|
||||
Value = value?.ToString();
|
||||
}
|
||||
|
||||
public string Label { get; set; }
|
||||
public TokenField Field { get; set; }
|
||||
public string Token { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
public class FieldSelectOption
|
||||
{
|
||||
public int Value { get; set; }
|
||||
@@ -82,4 +99,11 @@ namespace NzbDrone.Core.Annotations
|
||||
ApiKey,
|
||||
UserName
|
||||
}
|
||||
|
||||
public enum TokenField
|
||||
{
|
||||
Label,
|
||||
HelpText,
|
||||
HelpTextWarning
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,8 +332,8 @@ namespace NzbDrone.Core.Configuration
|
||||
return;
|
||||
}
|
||||
|
||||
// If SSL is enabled and a cert hash is still in the config file disable SSL
|
||||
if (EnableSsl && GetValue("SslCertHash", null).IsNotNullOrWhiteSpace())
|
||||
// If SSL is enabled and a cert hash is still in the config file or cert path is empty disable SSL
|
||||
if (EnableSsl && (GetValue("SslCertHash", null).IsNotNullOrWhiteSpace() || SslCertPath.IsNullOrWhiteSpace()))
|
||||
{
|
||||
SetValue("EnableSsl", false);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -41,6 +41,9 @@ namespace NzbDrone.Core.Download.Clients.Aria2
|
||||
[FieldDefinition(4, Label = "Secret token", 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));
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download.Clients.Flood.Models;
|
||||
using NzbDrone.Core.Indexers;
|
||||
@@ -56,7 +57,7 @@ namespace NzbDrone.Core.Download.Clients.Flood
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return result.Where(t => t.IsNotNullOrWhiteSpace());
|
||||
}
|
||||
|
||||
public override string Name => "Flood";
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
public enum QBittorrentContentLayout
|
||||
{
|
||||
Default = 0,
|
||||
Original = 1,
|
||||
Subfolder = 2
|
||||
}
|
||||
}
|
||||
@@ -265,6 +265,15 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
request.AddFormParameter("firstLastPiecePrio", true);
|
||||
}
|
||||
|
||||
if ((QBittorrentContentLayout)settings.ContentLayout == QBittorrentContentLayout.Original)
|
||||
{
|
||||
request.AddFormParameter("contentLayout", "Original");
|
||||
}
|
||||
else if ((QBittorrentContentLayout)settings.ContentLayout == QBittorrentContentLayout.Subfolder)
|
||||
{
|
||||
request.AddFormParameter("contentLayout", "Subfolder");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings)
|
||||
|
||||
@@ -62,6 +62,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
[FieldDefinition(10, Label = "First and Last First", Type = FieldType.Checkbox, HelpText = "Download first and last pieces first (qBittorrent 4.1.0+)")]
|
||||
public bool FirstAndLast { get; set; }
|
||||
|
||||
[FieldDefinition(13, Label = "DownloadClientQbittorrentSettingsContentLayout", Type = FieldType.Select, SelectOptions = typeof(QBittorrentContentLayout), HelpText = "DownloadClientQbittorrentSettingsContentLayoutHelpText")]
|
||||
public int ContentLayout { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Annotations;
|
||||
@@ -16,6 +19,7 @@ using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
@@ -24,7 +28,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public class GazelleGames : TorrentIndexerBase<GazelleGamesSettings>
|
||||
{
|
||||
public override string Name => "GazelleGames";
|
||||
public override string[] IndexerUrls => new string[] { "https://gazellegames.net/" };
|
||||
public override string[] IndexerUrls => new[] { "https://gazellegames.net/" };
|
||||
public override string Description => "GazelleGames (GGn) is a Private Torrent Tracker for GAMES";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
@@ -38,7 +42,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new GazelleGamesRequestGenerator() { Settings = Settings, Capabilities = Capabilities, HttpClient = _httpClient };
|
||||
return new GazelleGamesRequestGenerator(Settings, Capabilities);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
@@ -48,14 +52,13 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
};
|
||||
var caps = new IndexerCapabilities();
|
||||
|
||||
// Apple
|
||||
caps.Categories.AddCategoryMapping("Mac", NewznabStandardCategory.ConsoleOther, "Mac");
|
||||
caps.Categories.AddCategoryMapping("iOS", NewznabStandardCategory.PCMobileiOS, "iOS");
|
||||
caps.Categories.AddCategoryMapping("Apple Bandai Pippin", NewznabStandardCategory.ConsoleOther, "Apple Bandai Pippin");
|
||||
caps.Categories.AddCategoryMapping("Apple II", NewznabStandardCategory.ConsoleOther, "Apple II");
|
||||
|
||||
// Google
|
||||
caps.Categories.AddCategoryMapping("Android", NewznabStandardCategory.PCMobileAndroid, "Android");
|
||||
@@ -78,6 +81,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
caps.Categories.AddCategoryMapping("Nintendo GameCube", NewznabStandardCategory.ConsoleOther, "Nintendo GameCube");
|
||||
caps.Categories.AddCategoryMapping("Pokemon Mini", NewznabStandardCategory.ConsoleOther, "Pokemon Mini");
|
||||
caps.Categories.AddCategoryMapping("SNES", NewznabStandardCategory.ConsoleOther, "SNES");
|
||||
caps.Categories.AddCategoryMapping("Switch", NewznabStandardCategory.ConsoleOther, "Switch");
|
||||
caps.Categories.AddCategoryMapping("Virtual Boy", NewznabStandardCategory.ConsoleOther, "Virtual Boy");
|
||||
caps.Categories.AddCategoryMapping("Wii", NewznabStandardCategory.ConsoleWii, "Wii");
|
||||
caps.Categories.AddCategoryMapping("Wii U", NewznabStandardCategory.ConsoleWiiU, "Wii U");
|
||||
@@ -178,31 +182,62 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
caps.Categories.AddCategoryMapping("Retro - Other", NewznabStandardCategory.ConsoleOther, "Retro - Other");
|
||||
|
||||
// special categories (real categories/not platforms)
|
||||
caps.Categories.AddCategoryMapping("OST", NewznabStandardCategory.AudioOther, "OST");
|
||||
caps.Categories.AddCategoryMapping("Applications", NewznabStandardCategory.PC0day, "Applications");
|
||||
caps.Categories.AddCategoryMapping("E-Books", NewznabStandardCategory.BooksEBook, "E-Books");
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.PCGames, "Games");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PC0day, "Applications");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.BooksEBook, "E-Books");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.AudioOther, "OST");
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
protected override async Task Test(List<ValidationFailure> failures)
|
||||
{
|
||||
((GazelleGamesRequestGenerator)GetRequestGenerator()).FetchPasskey();
|
||||
await base.Test(failures);
|
||||
await FetchPasskey().ConfigureAwait(false);
|
||||
|
||||
await base.Test(failures).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task FetchPasskey()
|
||||
{
|
||||
var request = new HttpRequestBuilder($"{Settings.BaseUrl.Trim().TrimEnd('/')}/api.php")
|
||||
.Accept(HttpAccept.Json)
|
||||
.SetHeader("X-API-Key", Settings.Apikey)
|
||||
.AddQueryParam("request", "quick_user")
|
||||
.Build();
|
||||
|
||||
var indexResponse = await _httpClient.ExecuteAsync(request).ConfigureAwait(false);
|
||||
|
||||
var index = Json.Deserialize<GazelleGamesUserResponse>(indexResponse.Content);
|
||||
|
||||
if (index == null ||
|
||||
string.IsNullOrWhiteSpace(index.Status) ||
|
||||
index.Status != "success" ||
|
||||
string.IsNullOrWhiteSpace(index.Response.PassKey))
|
||||
{
|
||||
throw new IndexerAuthException("Failed to authenticate with GazelleGames.");
|
||||
}
|
||||
|
||||
// Set passkey on settings so it can be used to generate the download URL
|
||||
Settings.Passkey = index.Response.PassKey;
|
||||
}
|
||||
}
|
||||
|
||||
public class GazelleGamesRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public GazelleGamesSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
public IIndexerHttpClient HttpClient { get; set; }
|
||||
private readonly GazelleGamesSettings _settings;
|
||||
private readonly IndexerCapabilities _capabilities;
|
||||
|
||||
public GazelleGamesRequestGenerator(GazelleGamesSettings settings, IndexerCapabilities capabilities)
|
||||
{
|
||||
_settings = settings;
|
||||
_capabilities = capabilities;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories)));
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -211,7 +246,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories)));
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -220,7 +255,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories)));
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -229,7 +264,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories)));
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -238,61 +273,72 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories)));
|
||||
pageableRequests.Add(GetRequest(GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria)));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public void FetchPasskey()
|
||||
private IEnumerable<IndexerRequest> GetRequest(List<KeyValuePair<string, string>> parameters)
|
||||
{
|
||||
// GET on index for the passkey
|
||||
var request = RequestBuilder().Resource("api.php?request=quick_user").Build();
|
||||
var indexResponse = HttpClient.Execute(request);
|
||||
var index = Json.Deserialize<GazelleGamesUserResponse>(indexResponse.Content);
|
||||
if (index == null ||
|
||||
string.IsNullOrWhiteSpace(index.Status) ||
|
||||
index.Status != "success" ||
|
||||
string.IsNullOrWhiteSpace(index.Response.PassKey))
|
||||
{
|
||||
throw new Exception("Failed to authenticate with GazelleGames.");
|
||||
}
|
||||
|
||||
// Set passkey on settings so it can be used to generate the download URL
|
||||
Settings.Passkey = index.Response.PassKey;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetRequest(string parameters)
|
||||
{
|
||||
var req = RequestBuilder()
|
||||
.Resource($"api.php?{parameters}")
|
||||
var request = RequestBuilder()
|
||||
.Resource($"/api.php?{parameters.GetQueryString()}")
|
||||
.Build();
|
||||
|
||||
yield return new IndexerRequest(req);
|
||||
yield return new IndexerRequest(request);
|
||||
}
|
||||
|
||||
private HttpRequestBuilder RequestBuilder()
|
||||
{
|
||||
return new HttpRequestBuilder($"{Settings.BaseUrl.Trim().TrimEnd('/')}")
|
||||
return new HttpRequestBuilder($"{_settings.BaseUrl.Trim().TrimEnd('/')}")
|
||||
.Resource("/api.php")
|
||||
.Accept(HttpAccept.Json)
|
||||
.SetHeader("X-API-Key", Settings.Apikey);
|
||||
.SetHeader("X-API-Key", _settings.Apikey);
|
||||
}
|
||||
|
||||
private string GetBasicSearchParameters(string searchTerm, int[] categories)
|
||||
private List<KeyValuePair<string, string>> GetBasicSearchParameters(string searchTerm, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var parameters = "request=search&search_type=torrents&empty_groups=filled&order_by=time&order_way=desc";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||
var parameters = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
var searchType = Settings.SearchGroupNames ? "groupname" : "searchstr";
|
||||
{ "request", "search" },
|
||||
{ "search_type", "torrents" },
|
||||
{ "empty_groups", "filled" },
|
||||
{ "order_by", "time" },
|
||||
{ "order_way", "desc" }
|
||||
};
|
||||
|
||||
parameters += string.Format("&{1}={0}", searchTerm.Replace(".", " "), searchType);
|
||||
if (searchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
parameters.Add(
|
||||
_settings.SearchGroupNames ? "groupname" : "searchstr",
|
||||
searchTerm.Replace(".", " "));
|
||||
}
|
||||
|
||||
if (categories != null)
|
||||
if (searchCriteria.Categories != null)
|
||||
{
|
||||
foreach (var cat in Capabilities.Categories.MapTorznabCapsToTrackers(categories))
|
||||
var categoryMappings = _capabilities.Categories
|
||||
.MapTorznabCapsToTrackers(searchCriteria.Categories)
|
||||
.Distinct()
|
||||
.Where(x => !x.IsAllDigits())
|
||||
.ToList();
|
||||
|
||||
categoryMappings.ForEach(category => parameters.Add("artistcheck[]", category));
|
||||
}
|
||||
|
||||
if (searchCriteria.MinSize is > 0)
|
||||
{
|
||||
var minSize = searchCriteria.MinSize.Value / 1024L / 1024L;
|
||||
if (minSize > 0)
|
||||
{
|
||||
parameters += string.Format("&artistcheck[]={0}", cat);
|
||||
parameters.Add("sizesmall", minSize.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
if (searchCriteria.MaxSize is > 0)
|
||||
{
|
||||
var maxSize = searchCriteria.MaxSize.Value / 1024L / 1024L;
|
||||
if (maxSize > 0)
|
||||
{
|
||||
parameters.Add("sizeslarge", maxSize.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,66 +375,63 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
|
||||
var jsonResponse = new HttpResponse<GazelleGamesResponse>(indexerResponse.HttpResponse);
|
||||
|
||||
if (jsonResponse.Resource.Status != "success" ||
|
||||
string.IsNullOrWhiteSpace(jsonResponse.Resource.Status) ||
|
||||
jsonResponse.Resource.Response == null)
|
||||
jsonResponse.Resource.Response is not JObject response)
|
||||
{
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
Dictionary<string, GazelleGamesGroup> response;
|
||||
var groups = response.ToObject<Dictionary<int, GazelleGamesGroup>>(JsonSerializer.Create(Json.GetSerializerSettings()));
|
||||
|
||||
try
|
||||
foreach (var group in groups)
|
||||
{
|
||||
response = ((JObject)jsonResponse.Resource.Response).ToObject<Dictionary<string, GazelleGamesGroup>>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
foreach (var result in response)
|
||||
{
|
||||
Dictionary<string, GazelleGamesTorrent> torrents;
|
||||
|
||||
try
|
||||
{
|
||||
torrents = ((JObject)result.Value.Torrents).ToObject<Dictionary<string, GazelleGamesTorrent>>();
|
||||
}
|
||||
catch
|
||||
if (group.Value.Torrents is not JObject groupTorrents)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.Value.Torrents != null)
|
||||
var torrents = groupTorrents
|
||||
.ToObject<Dictionary<int, GazelleGamesTorrent>>(JsonSerializer.Create(Json.GetSerializerSettings()))
|
||||
.Where(t => t.Value.TorrentType.ToUpperInvariant() == "TORRENT")
|
||||
.ToList();
|
||||
|
||||
var categories = group.Value.Artists
|
||||
.SelectMany(a => _categories.MapTrackerCatDescToNewznab(a.Name))
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
foreach (var torrent in torrents)
|
||||
{
|
||||
var categories = result.Value.Artists.Select(a => a.Name);
|
||||
var torrentId = torrent.Key;
|
||||
var infoUrl = GetInfoUrl(group.Key, torrentId);
|
||||
|
||||
foreach (var torrent in torrents)
|
||||
if (categories.Length == 0)
|
||||
{
|
||||
var id = int.Parse(torrent.Key);
|
||||
|
||||
var infoUrl = GetInfoUrl(result.Key, id);
|
||||
|
||||
var release = new TorrentInfo()
|
||||
{
|
||||
Guid = infoUrl,
|
||||
Title = torrent.Value.ReleaseTitle,
|
||||
Files = torrent.Value.FileCount,
|
||||
Grabs = torrent.Value.Snatched,
|
||||
Size = long.Parse(torrent.Value.Size),
|
||||
DownloadUrl = GetDownloadUrl(id),
|
||||
InfoUrl = infoUrl,
|
||||
Seeders = torrent.Value.Seeders,
|
||||
Categories = _categories.MapTrackerCatDescToNewznab(categories.FirstOrDefault()),
|
||||
Peers = torrent.Value.Leechers + torrent.Value.Seeders,
|
||||
PublishDate = torrent.Value.Time.ToUniversalTime(),
|
||||
DownloadVolumeFactor = torrent.Value.FreeTorrent == GazelleGamesFreeTorrent.FreeLeech || torrent.Value.FreeTorrent == GazelleGamesFreeTorrent.Neutral || torrent.Value.LowSeedFL ? 0 : 1,
|
||||
UploadVolumeFactor = torrent.Value.FreeTorrent == GazelleGamesFreeTorrent.Neutral ? 0 : 1
|
||||
};
|
||||
|
||||
torrentInfos.Add(release);
|
||||
categories = _categories.MapTrackerCatToNewznab(torrent.Value.CategoryId.ToString()).ToArray();
|
||||
}
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Guid = infoUrl,
|
||||
InfoUrl = infoUrl,
|
||||
DownloadUrl = GetDownloadUrl(torrentId),
|
||||
Title = GetTitle(group.Value, torrent.Value),
|
||||
Categories = categories,
|
||||
Files = torrent.Value.FileCount,
|
||||
Size = long.Parse(torrent.Value.Size),
|
||||
Grabs = torrent.Value.Snatched,
|
||||
Seeders = torrent.Value.Seeders,
|
||||
Peers = torrent.Value.Leechers + torrent.Value.Seeders,
|
||||
PublishDate = torrent.Value.Time.ToUniversalTime(),
|
||||
Scene = torrent.Value.Scene == 1,
|
||||
DownloadVolumeFactor = torrent.Value.FreeTorrent is GazelleGamesFreeTorrent.FreeLeech or GazelleGamesFreeTorrent.Neutral || torrent.Value.LowSeedFL ? 0 : 1,
|
||||
UploadVolumeFactor = torrent.Value.FreeTorrent == GazelleGamesFreeTorrent.Neutral ? 0 : 1,
|
||||
MinimumSeedTime = 288000 // Minimum of 3 days and 8 hours (80 hours in total)
|
||||
};
|
||||
|
||||
torrentInfos.Add(release);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,6 +442,54 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static string GetTitle(GazelleGamesGroup group, GazelleGamesTorrent torrent)
|
||||
{
|
||||
var title = WebUtility.HtmlDecode(torrent.ReleaseTitle);
|
||||
|
||||
if (group.Year is > 0 && !title.Contains(group.Year.ToString()))
|
||||
{
|
||||
title += $" ({group.Year})";
|
||||
}
|
||||
|
||||
if (torrent.RemasterTitle.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
title += $" [{$"{torrent.RemasterTitle} {torrent.RemasterYear}".Trim()}]";
|
||||
}
|
||||
|
||||
var flags = new List<string>
|
||||
{
|
||||
$"{torrent.Format} {torrent.Encoding}".Trim()
|
||||
};
|
||||
|
||||
if (group.Artists is { Count: > 0 })
|
||||
{
|
||||
flags.AddIfNotNull(group.Artists.Select(a => a.Name).Join(", "));
|
||||
}
|
||||
|
||||
flags.AddIfNotNull(torrent.Language);
|
||||
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())
|
||||
{
|
||||
title += $" [{string.Join(" / ", flags)}]";
|
||||
}
|
||||
|
||||
if (torrent.GameDoxType.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
title += $" [{torrent.GameDoxType.Trim()}]";
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
private string GetDownloadUrl(int torrentId)
|
||||
{
|
||||
// AuthKey is required but not checked, just pass in a dummy variable
|
||||
@@ -413,7 +504,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return url.FullUri;
|
||||
}
|
||||
|
||||
private string GetInfoUrl(string groupId, int torrentId)
|
||||
private string GetInfoUrl(int groupId, int torrentId)
|
||||
{
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/torrents.php")
|
||||
@@ -444,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), Must have User 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; }
|
||||
@@ -466,8 +557,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public class GazelleGamesGroup
|
||||
{
|
||||
public List<GazelleGamesArtist> Artists { get; set; }
|
||||
public ReadOnlyCollection<GazelleGamesArtist> Artists { get; set; }
|
||||
public object Torrents { get; set; }
|
||||
public int? Year { get; set; }
|
||||
}
|
||||
|
||||
public class GazelleGamesArtist
|
||||
@@ -478,16 +570,30 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public class GazelleGamesTorrent
|
||||
{
|
||||
public int CategoryId { get; set; }
|
||||
public string Format { get; set; }
|
||||
public string Encoding { get; set; }
|
||||
public string Language { get; set; }
|
||||
public string Region { get; set; }
|
||||
public string RemasterYear { get; set; }
|
||||
public string RemasterTitle { get; set; }
|
||||
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; }
|
||||
public string Size { get; set; }
|
||||
public int? Snatched { get; set; }
|
||||
public int Seeders { get; set; }
|
||||
public int Leechers { get; set; }
|
||||
public string ReleaseTitle { get; set; }
|
||||
public DateTime Time { get; set; }
|
||||
public int FileCount { get; set; }
|
||||
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
|
||||
@@ -503,9 +609,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public enum GazelleGamesFreeTorrent
|
||||
{
|
||||
Normal,
|
||||
FreeLeech,
|
||||
Neutral,
|
||||
Either
|
||||
Normal = 0,
|
||||
FreeLeech = 1,
|
||||
Neutral = 2,
|
||||
Either = 3
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -244,17 +244,17 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
if (Settings.SearchInDescription)
|
||||
{
|
||||
parameters.Add("tor[srchIn][description]", "true");
|
||||
parameters.Set("tor[srchIn][description]", "true");
|
||||
}
|
||||
|
||||
if (Settings.SearchInSeries)
|
||||
{
|
||||
parameters.Add("tor[srchIn][series]", "true");
|
||||
parameters.Set("tor[srchIn][series]", "true");
|
||||
}
|
||||
|
||||
if (Settings.SearchInFilenames)
|
||||
{
|
||||
parameters.Add("tor[srchIn][filenames]", "true");
|
||||
parameters.Set("tor[srchIn][filenames]", "true");
|
||||
}
|
||||
|
||||
var catList = Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
|
||||
@@ -263,13 +263,28 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var index = 0;
|
||||
foreach (var cat in catList)
|
||||
{
|
||||
parameters.Add("tor[cat][" + index + "]", cat);
|
||||
parameters.Set("tor[cat][" + index + "]", cat);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters.Add("tor[cat][]", "0");
|
||||
parameters.Set("tor[cat][]", "0");
|
||||
}
|
||||
|
||||
if (searchCriteria.MinSize is > 0)
|
||||
{
|
||||
parameters.Set("tor[minSize]", searchCriteria.MinSize.Value.ToString());
|
||||
}
|
||||
|
||||
if (searchCriteria.MaxSize is > 0)
|
||||
{
|
||||
parameters.Set("tor[maxSize]", searchCriteria.MaxSize.Value.ToString());
|
||||
}
|
||||
|
||||
if (searchCriteria.MinSize is > 0 || searchCriteria.MaxSize is > 0)
|
||||
{
|
||||
parameters.Set("tor[unit]", "1");
|
||||
}
|
||||
|
||||
var searchUrl = Settings.BaseUrl + "tor/js/loadSearchJSONbasic.php";
|
||||
@@ -394,6 +409,8 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
release.Title = item.Title;
|
||||
release.Description = item.Description;
|
||||
|
||||
release.BookTitle = item.Title;
|
||||
|
||||
if (item.AuthorInfo != null)
|
||||
{
|
||||
var authorInfo = JsonConvert.DeserializeObject<Dictionary<string, string>>(item.AuthorInfo);
|
||||
@@ -402,6 +419,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
if (author.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
release.Title += " by " + author;
|
||||
release.Author = author;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,13 +420,13 @@ 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")]
|
||||
public bool UseFreeleechToken { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Freeload Only", Type = FieldType.Checkbox, Advanced = true, HelpTextWarning = "Search freeload torrents only. End date: 6 January 2024, 23:59 UTC.")]
|
||||
[FieldDefinition(4, Label = "Freeload Only", Type = FieldType.Checkbox, Advanced = true, HelpTextWarning = "Search freeload torrents only. End date: 31 January 2024, 23:59 UTC.")]
|
||||
public bool FreeloadOnly { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Indexers.Definitions.UNIT3D
|
||||
{
|
||||
private static readonly Unit3dSettingsValidator Validator = new ();
|
||||
|
||||
[FieldDefinition(2, Label = "API Key", HelpText = "Site API Key generated in My Security", Privacy = PrivacyLevel.ApiKey)]
|
||||
[FieldDefinition(2, Label = "ApiKey", HelpText = "Site API Key generated in My Security", Privacy = PrivacyLevel.ApiKey)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -388,11 +388,11 @@ namespace NzbDrone.Core.Indexers
|
||||
if (webException.Message.Contains("502") || webException.Message.Contains("503") ||
|
||||
webException.Message.Contains("504") || webException.Message.Contains("timed out"))
|
||||
{
|
||||
_logger.Warn("{0} server is currently unavailable. {1} {2}", this, url, webException.Message);
|
||||
_logger.Warn(webException, "{0} server is currently unavailable. {1} {2}", this, url, webException.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("{0} {1} {2}", this, url, webException.Message);
|
||||
_logger.Warn(webException, "{0} {1} {2}", this, url, webException.Message);
|
||||
}
|
||||
}
|
||||
catch (TooManyRequestsException ex)
|
||||
@@ -402,7 +402,7 @@ namespace NzbDrone.Core.Indexers
|
||||
var retryTime = ex.RetryAfter != TimeSpan.Zero ? ex.RetryAfter : minimumBackoff;
|
||||
|
||||
_indexerStatusService.RecordFailure(Definition.Id, retryTime);
|
||||
_logger.Warn("Request Limit reached for {0}. Disabled for {1}", this, retryTime);
|
||||
_logger.Warn(ex, "Request Limit reached for {0}. Disabled for {1}", this, retryTime);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
@@ -411,18 +411,18 @@ namespace NzbDrone.Core.Indexers
|
||||
|
||||
if (ex.Response.HasHttpServerError)
|
||||
{
|
||||
_logger.Warn("Unable to connect to {0} at [{1}]. Indexer's server is unavailable. Try again later. {2}", this, url, ex.Message);
|
||||
_logger.Warn(ex, "Unable to connect to {0} at [{1}]. Indexer's server is unavailable. Try again later. {2}", this, url, ex.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("{0} {1}", this, ex.Message);
|
||||
_logger.Warn(ex, "{0} {1}", this, ex.Message);
|
||||
}
|
||||
}
|
||||
catch (RequestLimitReachedException ex)
|
||||
{
|
||||
result.Queries.Add(new IndexerQueryResult { Response = ex.Response.HttpResponse });
|
||||
_indexerStatusService.RecordFailure(Definition.Id, minimumBackoff);
|
||||
_logger.Warn("Request Limit reached for {0}. Disabled for {1}", this, minimumBackoff);
|
||||
_logger.Warn(ex, "Request Limit reached for {0}. Disabled for {1}", this, minimumBackoff);
|
||||
}
|
||||
catch (IndexerAuthException ex)
|
||||
{
|
||||
@@ -733,7 +733,7 @@ namespace NzbDrone.Core.Indexers
|
||||
}
|
||||
catch (WebException webException)
|
||||
{
|
||||
_logger.Warn("Unable to connect to indexer.");
|
||||
_logger.Warn(webException, "Unable to connect to indexer.");
|
||||
|
||||
if (webException.Status is WebExceptionStatus.NameResolutionFailure or WebExceptionStatus.ConnectFailure)
|
||||
{
|
||||
|
||||
@@ -21,13 +21,13 @@ namespace NzbDrone.Core.Indexers
|
||||
|
||||
public class IndexerBaseSettings
|
||||
{
|
||||
[FieldDefinition(1, Type = FieldType.Number, Label = "Query Limit", HelpText = "The number of max queries as specified by the respective unit that Prowlarr will allow to the site", Advanced = true)]
|
||||
[FieldDefinition(1, Type = FieldType.Number, Label = "IndexerSettingsQueryLimit", HelpText = "IndexerSettingsQueryLimitHelpText", Advanced = true)]
|
||||
public int? QueryLimit { get; set; }
|
||||
|
||||
[FieldDefinition(2, Type = FieldType.Number, Label = "Grab Limit", HelpText = "The number of max grabs as specified by the respective unit that Prowlarr will allow to the site", Advanced = true)]
|
||||
[FieldDefinition(2, Type = FieldType.Number, Label = "IndexerSettingsGrabLimit", HelpText = "IndexerSettingsGrabLimitHelpText", Advanced = true)]
|
||||
public int? GrabLimit { get; set; }
|
||||
|
||||
[FieldDefinition(3, Type = FieldType.Select, SelectOptions = typeof(IndexerLimitsUnit), Label = "Limits Unit", HelpText = "The unit of time for counting limits per indexer", Advanced = true)]
|
||||
[FieldDefinition(3, Type = FieldType.Select, Label = "IndexerSettingsLimitsUnit", SelectOptions = typeof(IndexerLimitsUnit), HelpText = "IndexerSettingsLimitsUnitHelpText", Advanced = true)]
|
||||
public int LimitsUnit { get; set; } = (int)IndexerLimitsUnit.Day;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,16 +52,16 @@ namespace NzbDrone.Core.Indexers
|
||||
|
||||
public class IndexerTorrentBaseSettings
|
||||
{
|
||||
[FieldDefinition(1, Type = FieldType.Number, Label = "Apps Minimum Seeders", HelpText = "Minimum seeders required by the Applications for the indexer to grab, empty is Sync profile's default", Advanced = true)]
|
||||
[FieldDefinition(1, Type = FieldType.Number, Label = "IndexerSettingsAppsMinimumSeeders", HelpText = "IndexerSettingsAppsMinimumSeedersHelpText", Advanced = true)]
|
||||
public int? AppMinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(2, Type = FieldType.Textbox, Label = "Seed Ratio", HelpText = "The ratio a torrent should reach before stopping, empty is app's default", Advanced = true)]
|
||||
[FieldDefinition(2, Type = FieldType.Textbox, Label = "IndexerSettingsSeedRatio", HelpText = "IndexerSettingsSeedRatioHelpText")]
|
||||
public double? SeedRatio { get; set; }
|
||||
|
||||
[FieldDefinition(3, Type = FieldType.Number, Label = "Seed Time", HelpText = "The time a torrent should be seeded before stopping, empty is app's default", Unit = "minutes", Advanced = true)]
|
||||
[FieldDefinition(3, Type = FieldType.Number, Label = "IndexerSettingsSeedTime", Unit = "minutes", HelpText = "IndexerSettingsSeedTimeHelpText", Advanced = true)]
|
||||
public int? SeedTime { get; set; }
|
||||
|
||||
[FieldDefinition(4, Type = FieldType.Number, Label = "Pack Seed Time", HelpText = "The time a pack (season or discography) torrent should be seeded before stopping, empty is app's default", Unit = "minutes", Advanced = true)]
|
||||
[FieldDefinition(4, Type = FieldType.Number, Label = "IndexerSettingsPackSeedTime", HelpText = "IndexerSettingsPackSeedTimeIndexerHelpText", Unit = "minutes", Advanced = true)]
|
||||
public int? PackSeedTime { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,26 +10,26 @@ namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
private static readonly List<string> Trackers = new ()
|
||||
{
|
||||
"udp://tracker.opentrackr.org:1337/announce",
|
||||
"https://tracker2.ctix.cn:443/announce",
|
||||
"https://tracker1.520.jp:443/announce",
|
||||
"http://tracker.opentrackr.org:1337/announce",
|
||||
"udp://tracker.auctor.tv:6969/announce",
|
||||
"udp://opentracker.i2p.rocks:6969/announce",
|
||||
"udp://open.tracker.cl:1337/announce",
|
||||
"https://opentracker.i2p.rocks:443/announce",
|
||||
"udp://open.demonii.com:1337/announce",
|
||||
"udp://tracker.openbittorrent.com:6969/announce",
|
||||
"http://tracker.openbittorrent.com:80/announce",
|
||||
"udp://open.stealth.si:80/announce",
|
||||
"udp://exodus.desync.com:6969/announce",
|
||||
"udp://tracker.torrent.eu.org:451/announce",
|
||||
"udp://tracker1.bt.moack.co.kr:80/announce",
|
||||
"udp://tracker-udp.gbitt.info:80/announce",
|
||||
"udp://tracker.moeking.me:6969/announce",
|
||||
"udp://explodie.org:6969/announce",
|
||||
"https://tracker.gbitt.info:443/announce",
|
||||
"http://tracker.gbitt.info:80/announce",
|
||||
"http://bt.endpot.com:80/announce",
|
||||
"udp://exodus.desync.com:6969/announce",
|
||||
"udp://uploads.gamecoast.net:6969/announce",
|
||||
"udp://tracker1.bt.moack.co.kr:80/announce",
|
||||
"udp://tracker.tiny-vps.com:6969/announce",
|
||||
"udp://tracker.auctor.tv:6969/announce",
|
||||
"udp://tk1.trackerservers.com:8080/announce"
|
||||
"udp://tracker.theoks.net:6969/announce",
|
||||
"udp://tracker.skyts.net:6969/announce",
|
||||
"udp://tracker-udp.gbitt.info:80/announce",
|
||||
"udp://open.tracker.ink:6969/announce",
|
||||
"udp://movies.zsw.ca:6969/announce"
|
||||
};
|
||||
|
||||
public static string BuildPublicMagnetLink(string infoHash, string releaseTitle)
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Indexers.Settings
|
||||
Cookie = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(2, Label = "Cookie", HelpText = "Site Cookie", HelpLink = "https://wiki.servarr.com/useful-tools#finding-cookies", Privacy = PrivacyLevel.Password)]
|
||||
[FieldDefinition(2, Label = "IndexerSettingsCookie", HelpText = "IndexerSettingsCookieHelpText", HelpLink = "https://wiki.servarr.com/useful-tools#finding-cookies", Privacy = PrivacyLevel.Password)]
|
||||
public string Cookie { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace NzbDrone.Core.Indexers.Settings
|
||||
{
|
||||
private static readonly NoAuthSettingsValidator<NoAuthTorrentBaseSettings> Validator = new ();
|
||||
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
[FieldDefinition(1, Label = "IndexerSettingsBaseUrl", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "IndexerSettingsBaseUrlHelpText")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(20)]
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"ConnectSettings": "ربط الإعدادات",
|
||||
"CouldNotConnectSignalR": "تعذر الاتصال بـ SignalR ، لن يتم تحديث واجهة المستخدم",
|
||||
"Dates": "تواريخ",
|
||||
"DBMigration": "ترحيل DB",
|
||||
"DatabaseMigration": "ترحيل DB",
|
||||
"Delete": "حذف",
|
||||
"Details": "تفاصيل",
|
||||
"Donations": "التبرعات",
|
||||
@@ -150,7 +150,7 @@
|
||||
"RemovingTag": "إزالة العلامة",
|
||||
"Reset": "إعادة تعيين",
|
||||
"ResetAPIKey": "إعادة تعيين مفتاح API",
|
||||
"RSSIsNotSupportedWithThisIndexer": "لا يتم دعم RSS مع هذا المفهرس",
|
||||
"RssIsNotSupportedWithThisIndexer": "لا يتم دعم RSS مع هذا المفهرس",
|
||||
"Save": "حفظ",
|
||||
"ScriptPath": "مسار البرنامج النصي",
|
||||
"SettingsEnableColorImpairedMode": "تفعيل وضع ضعف الألوان",
|
||||
@@ -251,13 +251,13 @@
|
||||
"NoChanges": "لا تغيرات",
|
||||
"NoLeaveIt": "لا ، اتركها",
|
||||
"PendingChangesDiscardChanges": "تجاهل التغييرات واترك",
|
||||
"RSS": "RSS",
|
||||
"Rss": "RSS",
|
||||
"System": "النظام",
|
||||
"TagIsNotUsedAndCanBeDeleted": "العلامة غير مستخدمة ويمكن حذفها",
|
||||
"Tags": "العلامات",
|
||||
"TagsHelpText": "ينطبق على الأفلام التي تحتوي على علامة مطابقة واحدة على الأقل",
|
||||
"UISettings": "إعدادات واجهة المستخدم",
|
||||
"UnableToLoadDownloadClients": "تعذر تحميل عملاء التنزيل",
|
||||
"DownloadClientsLoadError": "تعذر تحميل عملاء التنزيل",
|
||||
"UnableToLoadTags": "تعذر تحميل العلامات",
|
||||
"UnableToLoadUISettings": "تعذر تحميل إعدادات واجهة المستخدم",
|
||||
"UpdateCheckStartupTranslocationMessage": "لا يمكن تثبيت التحديث لأن مجلد بدء التشغيل \"{0}\" موجود في مجلد App Translocation.",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"CustomFilters": "Персонализирани филтри",
|
||||
"Date": "Дата",
|
||||
"Dates": "Дати",
|
||||
"DBMigration": "DB миграция",
|
||||
"DatabaseMigration": "DB миграция",
|
||||
"Delete": "Изтрий",
|
||||
"DeleteDownloadClient": "Изтриване на клиент за изтегляне",
|
||||
"DeleteDownloadClientMessageText": "Наистина ли искате да изтриете клиента за изтегляне '{0}'?",
|
||||
@@ -170,7 +170,7 @@
|
||||
"RemovingTag": "Премахване на етикет",
|
||||
"Reset": "Нулиране",
|
||||
"Retention": "Задържане",
|
||||
"RSS": "RSS",
|
||||
"Rss": "RSS",
|
||||
"Warn": "Предупреждавайте",
|
||||
"NoTagsHaveBeenAddedYet": "Все още не са добавени етикети",
|
||||
"NotificationTriggers": "Задействания за уведомяване",
|
||||
@@ -188,7 +188,7 @@
|
||||
"Priority": "Приоритет",
|
||||
"SettingsLongDateFormat": "Формат с дълга дата",
|
||||
"SettingsShowRelativeDates": "Показване на относителни дати",
|
||||
"UnableToLoadDownloadClients": "Клиентите за изтегляне не могат да се заредят",
|
||||
"DownloadClientsLoadError": "Клиентите за изтегляне не могат да се заредят",
|
||||
"Logging": "Регистрация",
|
||||
"Exception": "Изключение",
|
||||
"MovieIndexScrollBottom": "Индекс на филма: Превъртане отдолу",
|
||||
@@ -296,7 +296,7 @@
|
||||
"Restart": "Рестартирам",
|
||||
"RestartNow": "Рестартирай сега",
|
||||
"Restore": "Възстанови",
|
||||
"RSSIsNotSupportedWithThisIndexer": "RSS не се поддържа с този индексатор",
|
||||
"RssIsNotSupportedWithThisIndexer": "RSS не се поддържа с този индексатор",
|
||||
"Wiki": "Wiki",
|
||||
"PageSizeHelpText": "Брой елементи за показване на всяка страница",
|
||||
"Password": "Парола",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Add": "Afegiu",
|
||||
"Add": "Afegeix",
|
||||
"Actions": "Accions",
|
||||
"AcceptConfirmationModal": "Accepta el modal de confirmació",
|
||||
"About": "Quant a",
|
||||
@@ -19,7 +19,7 @@
|
||||
"ScriptPath": "Camí de l'script",
|
||||
"Search": "Cerca",
|
||||
"Files": "Fitxers",
|
||||
"SettingsEnableColorImpairedModeHelpText": "Estil alternat per permetre als usuaris amb problemes de color distingir millor la informació codificada per colors",
|
||||
"SettingsEnableColorImpairedModeHelpText": "Estil alternat per a permetre als usuaris amb problemes de color distingir millor la informació codificada per colors",
|
||||
"TagIsNotUsedAndCanBeDeleted": "L'etiqueta no està en ús i es pot suprimir",
|
||||
"TagsSettingsSummary": "Consulta totes les etiquetes i com s'utilitzen. Les etiquetes no utilitzades es poden eliminar",
|
||||
"Tasks": "Tasques",
|
||||
@@ -33,18 +33,18 @@
|
||||
"AddDownloadClient": "Afegeix un client de descàrrega",
|
||||
"Added": "Afegit",
|
||||
"Age": "Edat",
|
||||
"All": "Tots",
|
||||
"All": "Tot",
|
||||
"Analytics": "Anàlisi",
|
||||
"ApiKey": "Clau API",
|
||||
"AppDataDirectory": "Directori AppData",
|
||||
"AppDataLocationHealthCheckMessage": "No es podrà actualitzar per evitar que s'eliminin AppData a l'actualització",
|
||||
"AppDataLocationHealthCheckMessage": "No es podrà actualitzar per a evitar que s'eliminin AppData a l'actualització",
|
||||
"Authentication": "Autenticació",
|
||||
"Torrents": "Torrents",
|
||||
"Type": "Tipus",
|
||||
"UILanguageHelpTextWarning": "Es requereix una recàrrega del navegador",
|
||||
"UISettings": "Configuració de la interfície",
|
||||
"UnableToLoadBackups": "No es poden carregar còpies de seguretat",
|
||||
"UnableToLoadDownloadClients": "No es poden carregar els clients de baixada",
|
||||
"DownloadClientsLoadError": "No es poden carregar els clients de baixada",
|
||||
"UnableToLoadTags": "No es poden carregar les etiquetes",
|
||||
"UnableToLoadUISettings": "No es pot carregar la configuració de la IU",
|
||||
"UnselectAll": "Desseleccioneu-ho tot",
|
||||
@@ -78,11 +78,11 @@
|
||||
"Reddit": "Reddit",
|
||||
"System": "Sistema",
|
||||
"Username": "Nom d'usuari",
|
||||
"Duration": "durada",
|
||||
"Duration": "Durada",
|
||||
"EditIndexer": "Edita l'indexador",
|
||||
"EnableAutomaticSearch": "Activa la cerca automàtica",
|
||||
"Enabled": "Habilitat",
|
||||
"Error": "error",
|
||||
"Error": "Error",
|
||||
"ErrorLoadingContents": "S'ha produït un error en carregar el contingut",
|
||||
"Events": "Esdeveniments",
|
||||
"ExistingTag": "Etiqueta existent",
|
||||
@@ -143,7 +143,7 @@
|
||||
"SettingsShortDateFormat": "Format de data curta",
|
||||
"BindAddress": "Adreça d'enllaç",
|
||||
"Database": "Base de dades",
|
||||
"Ended": "S'ha acabat",
|
||||
"Ended": "Acabat",
|
||||
"SettingsTimeFormat": "Format horari",
|
||||
"YesCancel": "Si, cancel·la",
|
||||
"Automatic": "Automàtic",
|
||||
@@ -168,11 +168,11 @@
|
||||
"CustomFilters": "Filtres personalitzats",
|
||||
"Date": "Data",
|
||||
"Dates": "Dates",
|
||||
"DBMigration": "Migració de BD",
|
||||
"DatabaseMigration": "Migració de BD",
|
||||
"Delete": "Suprimeix",
|
||||
"DeleteNotificationMessageText": "Esteu segur que voleu suprimir la notificació '{0}'?",
|
||||
"DeleteNotificationMessageText": "Esteu segur que voleu suprimir la notificació '{name}'?",
|
||||
"DeleteTag": "Suprimeix l'etiqueta",
|
||||
"DeleteTagMessageText": "Esteu segur que voleu suprimir l'etiqueta '{0}'?",
|
||||
"DeleteTagMessageText": "Esteu segur que voleu suprimir l'etiqueta '{label}'?",
|
||||
"Details": "Detalls",
|
||||
"Disabled": "Desactivat",
|
||||
"DownloadClientStatusCheckAllClientMessage": "Tots els clients de descàrrega no estan disponibles a causa d'errors",
|
||||
@@ -205,7 +205,7 @@
|
||||
"RestartRequiredHelpTextWarning": "Cal reiniciar perquè tingui efecte",
|
||||
"Restore": "Restaura",
|
||||
"RestoreBackup": "Restaura còpia de seguretat",
|
||||
"RSS": "RSS",
|
||||
"Rss": "RSS",
|
||||
"Save": "Desa",
|
||||
"SaveChanges": "Desa els canvis",
|
||||
"Security": "Seguretat",
|
||||
@@ -251,7 +251,7 @@
|
||||
"Level": "Nivell",
|
||||
"LogFiles": "Fitxers de registre",
|
||||
"Logging": "Registre",
|
||||
"MappedDrivesRunningAsService": "Les unitats de xarxa assignades no estan disponibles quan s'executen com a servei de Windows. Si us plau, consulteu les PMF per obtenir més informació",
|
||||
"MappedDrivesRunningAsService": "Les unitats de xarxa assignades no estan disponibles quan s'executen com a servei de Windows. Si us plau, consulteu les PMF per a obtenir més informació",
|
||||
"Mechanism": "Mecanisme",
|
||||
"MIA": "MIA",
|
||||
"Wiki": "Wiki",
|
||||
@@ -260,9 +260,9 @@
|
||||
"Branch": "Branca",
|
||||
"Connections": "Connexions",
|
||||
"ConnectSettings": "Configuració de connexió",
|
||||
"DeleteBackupMessageText": "Esteu segur que voleu suprimir la còpia de seguretat '{0}'?",
|
||||
"DeleteBackupMessageText": "Esteu segur que voleu suprimir la còpia de seguretat '{name}'?",
|
||||
"DeleteDownloadClient": "Suprimeix el client de descàrrega",
|
||||
"DeleteDownloadClientMessageText": "Esteu segur que voleu suprimir el client de baixada '{0}'?",
|
||||
"DeleteDownloadClientMessageText": "Esteu segur que voleu suprimir el client de baixada '{name}'?",
|
||||
"Discord": "Discord",
|
||||
"Docker": "Docker",
|
||||
"Donations": "Donacions",
|
||||
@@ -274,7 +274,7 @@
|
||||
"Hostname": "Nom d'amfitrió",
|
||||
"IgnoredAddresses": "Adreces ignorades",
|
||||
"ProxyUsernameHelpText": "Només cal que introduïu un nom d'usuari i una contrasenya si cal. Deixeu-los en blanc en cas contrari.",
|
||||
"RSSIsNotSupportedWithThisIndexer": "RSS no és compatible amb aquest indexador",
|
||||
"RssIsNotSupportedWithThisIndexer": "RSS no és compatible amb aquest indexador",
|
||||
"SaveSettings": "Desa la configuració",
|
||||
"Seeders": "Llavors",
|
||||
"SelectAll": "Selecciona-ho tot",
|
||||
@@ -300,10 +300,10 @@
|
||||
"View": "Visualitza",
|
||||
"Yesterday": "Ahir",
|
||||
"ApplicationStatusCheckSingleClientMessage": "Llistes no disponibles a causa d'errors: {0}",
|
||||
"AnalyticsEnabledHelpText": "Envieu informació anònima d'ús i errors als servidors de {appName}. Això inclou informació sobre el vostre navegador, quines pàgines {appName} WebUI feu servir, informes d'errors, així com el sistema operatiu i la versió del temps d'execució. Utilitzarem aquesta informació per prioritzar les funcions i les correccions d'errors.",
|
||||
"AnalyticsEnabledHelpText": "Envieu informació anònima d'ús i errors als servidors de {appName}. Això inclou informació sobre el vostre navegador, quines pàgines {appName} WebUI feu servir, informes d'errors, així com el sistema operatiu i la versió de l'entorn d'execució. Utilitzarem aquesta informació per a prioritzar les funcions i les correccions d'errors.",
|
||||
"HistoryCleanupDaysHelpTextWarning": "Els fitxers de la paperera de reciclatge més antics que el nombre de dies seleccionat es netejaran automàticament",
|
||||
"UnableToAddANewAppProfilePleaseTryAgain": "No es pot afegir un perfil de qualitat nou, torneu-ho a provar.",
|
||||
"BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData del {appName}",
|
||||
"BackupFolderHelpText": "Els camins relatius estaran sota el directori AppData de {appName}",
|
||||
"AllIndexersHiddenDueToFilter": "Totes les pel·lícules estan ocultes a causa del filtre aplicat.",
|
||||
"EnableRss": "Activa RSS",
|
||||
"Grabs": "Captura",
|
||||
@@ -312,11 +312,11 @@
|
||||
"Application": "Aplicacions",
|
||||
"Applications": "Aplicacions",
|
||||
"ApplicationStatusCheckAllClientMessage": "Totes les llistes no estan disponibles a causa d'errors",
|
||||
"AuthenticationMethodHelpText": "Requereix nom d'usuari i contrasenya per accedir al radar",
|
||||
"AuthenticationMethodHelpText": "Es requereix nom d'usuari i contrasenya per a accedir a {appName}",
|
||||
"ApplicationLongTermStatusCheckAllClientMessage": "Tots els indexadors no estan disponibles a causa d'errors durant més de 6 hores",
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "Els indexadors no estan disponibles a causa d'errors durant més de 6 hores: {0}",
|
||||
"BindAddressHelpText": "Adreça IP vàlida, localhost o '*' per a totes les interfícies",
|
||||
"BranchUpdate": "Branca que s'utilitza per actualitzar {appName}",
|
||||
"BranchUpdate": "Branca que s'utilitza per a actualitzar {appName}",
|
||||
"Connect": "Notificacions",
|
||||
"DeleteApplicationMessageText": "Esteu segur que voleu suprimir la notificació '{0}'?",
|
||||
"DeleteIndexerProxyMessageText": "Esteu segur que voleu suprimir la llista '{0}'?",
|
||||
@@ -347,35 +347,89 @@
|
||||
"ApplicationUrlHelpText": "URL extern d'aquesta aplicació, inclòs http(s)://, port i URL base",
|
||||
"ApplyTagsHelpTextAdd": "Afegeix: afegeix les etiquetes a la llista d'etiquetes existent",
|
||||
"ApplyTagsHelpTextHowToApplyApplications": "Com aplicar etiquetes a les pel·lícules seleccionades",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Com aplicar etiquetes a les pel·lícules seleccionades",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Com aplicar etiquetes als indexadors seleccionats",
|
||||
"ApplyTagsHelpTextRemove": "Eliminar: elimina les etiquetes introduïdes",
|
||||
"DeleteSelectedApplicationsMessageText": "Esteu segur que voleu suprimir l'indexador '{0}'?",
|
||||
"Label": "Etiqueta",
|
||||
"ApplyTagsHelpTextReplace": "Substituïu: substituïu les etiquetes per les etiquetes introduïdes (no introduïu cap etiqueta per esborrar totes les etiquetes)",
|
||||
"DeleteSelectedDownloadClients": "Suprimeix el client de descàrrega",
|
||||
"ApplyTagsHelpTextReplace": "Substitució: substituïu les etiquetes per les etiquetes introduïdes (no introduïu cap etiqueta per a esborrar totes les etiquetes)",
|
||||
"DeleteSelectedDownloadClients": "Suprimeix el(s) client(s) de baixada",
|
||||
"Genre": "Gèneres",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Esteu segur que voleu suprimir l'indexador '{0}'?",
|
||||
"DeleteSelectedIndexersMessageText": "Esteu segur que voleu suprimir l'indexador '{0}'?",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Esteu segur que voleu suprimir {count} client(s) de baixada seleccionat(s)?",
|
||||
"DeleteSelectedIndexersMessageText": "Esteu segur que voleu suprimir {count} indexador(s) seleccionat(s)?",
|
||||
"DownloadClientPriorityHelpText": "Prioritzeu diversos clients de baixada. S'utilitza round-robin per a clients amb la mateixa prioritat.",
|
||||
"More": "Més",
|
||||
"Season": "temporada",
|
||||
"Season": "Temporada",
|
||||
"Theme": "Tema",
|
||||
"Track": "Traça",
|
||||
"Year": "Any",
|
||||
"UpdateAvailable": "Nova actualització disponible",
|
||||
"ConnectionLostReconnect": "Radarr intentarà connectar-se automàticament, o podeu fer clic a recarregar.",
|
||||
"ConnectionLostToBackend": "Radarr ha perdut la connexió amb el backend i s'haurà de tornar a carregar per restaurar la funcionalitat.",
|
||||
"ConnectionLostReconnect": "{appName} intentarà connectar-se automàticament, o podeu fer clic a recarregar.",
|
||||
"ConnectionLostToBackend": "{appName} ha perdut la connexió amb el backend i s'haurà de tornar a carregar per a restaurar la funcionalitat.",
|
||||
"RecentChanges": "Canvis recents",
|
||||
"WhatsNew": "Que hi ha de nou?",
|
||||
"minutes": "Minuts",
|
||||
"DeleteAppProfileMessageText": "Esteu segur que voleu suprimir el perfil de qualitat {0}",
|
||||
"NotificationStatusSingleClientHealthCheckMessage": "Llistes no disponibles a causa d'errors: {0}",
|
||||
"AddConnection": "Edita la col·lecció",
|
||||
"NotificationStatusAllClientHealthCheckMessage": "Totes les llistes no estan disponibles a causa d'errors",
|
||||
"AddConnection": "Afegeix una connexió",
|
||||
"NotificationStatusAllClientHealthCheckMessage": "Totes les notificacions no estan disponibles a causa d'errors",
|
||||
"AuthBasic": "Basic (finestra emergent del navegador)",
|
||||
"AuthForm": "Formularis (pàgina d'inici de sessió)",
|
||||
"DisabledForLocalAddresses": "Desactivat per a adreces locals",
|
||||
"None": "Cap",
|
||||
"ResetAPIKeyMessageText": "Esteu segur que voleu restablir la clau de l'API?",
|
||||
"RestartProwlarr": "Reinicia {appName}"
|
||||
"RestartProwlarr": "Reinicia {appName}",
|
||||
"AuthenticationRequired": "Autenticació necessària",
|
||||
"CountDownloadClientsSelected": "{count} client(s) de baixada seleccionat(s)",
|
||||
"NoDownloadClientsFound": "No s'han trobat clients de baixada",
|
||||
"AuthenticationRequiredWarning": "Per a evitar l'accés remot sense autenticació, ara {appName} requereix que l'autenticació estigui activada. Opcionalment, podeu desactivar l'autenticació des d'adreces locals.",
|
||||
"AppUpdatedVersion": "{appName} s'ha actualitzat a la versió `{version}`, per tal d'obtenir els darrers canvis, haureu de tornar a carregar {appName}",
|
||||
"AppUpdated": "{appName} Actualitzada",
|
||||
"ApplyChanges": "Aplica els canvis",
|
||||
"Implementation": "Implementació",
|
||||
"OnHealthRestored": "Al resoldre les incidències",
|
||||
"ManageDownloadClients": "Gestiona els clients de descàrrega",
|
||||
"AuthenticationRequiredHelpText": "Canvia per a quines sol·licituds cal autenticar. No canvieu tret que entengueu els riscos.",
|
||||
"CountIndexersSelected": "S'han seleccionat {count} indexador(s)",
|
||||
"EditDownloadClientImplementation": "Edita el client de baixada - {implementationName}",
|
||||
"EditIndexerImplementation": "Edita l'indexador - {implementationName}",
|
||||
"EditSelectedDownloadClients": "Editeu els clients de descàrrega seleccionats",
|
||||
"EditSelectedIndexers": "Edita els indexadors seleccionats",
|
||||
"IndexerDownloadClientHealthCheckMessage": "Indexadors amb clients de baixada no vàlids: {0}.",
|
||||
"AddCustomFilter": "Afegeix un filtre personalitzat",
|
||||
"AddDownloadClientImplementation": "Afegeix un client de descàrrega - {implementationName}",
|
||||
"AddIndexerImplementation": "Afegeix un indexador - {implementationName}",
|
||||
"AddConnectionImplementation": "Afegeix una connexió - {implementationName}",
|
||||
"InvalidUILanguage": "La vostra IU està configurada en un idioma no vàlid, corregiu-lo i deseu la configuració",
|
||||
"NoHistoryFound": "No s'ha trobat cap historial",
|
||||
"NoIndexersFound": "No s'han trobat indexadors",
|
||||
"OnHealthRestoredHelpText": "Al resoldre les incidències",
|
||||
"AuthenticationMethod": "Mètode d'autenticació",
|
||||
"AuthenticationMethodHelpTextWarning": "Seleccioneu un mètode d'autenticació vàlid",
|
||||
"AuthenticationRequiredPasswordHelpTextWarning": "Introduïu una contrasenya nova",
|
||||
"DefaultNameCopiedProfile": "{name} - Còpia",
|
||||
"DownloadClientQbittorrentSettingsContentLayout": "Disseny de contingut",
|
||||
"DownloadClientQbittorrentSettingsContentLayoutHelpText": "Si s'utilitza el disseny de contingut de qBittorrent s'utilitza el disseny original del torrent o es crea una subcarpeta (qBittorrent 4.3.2+)",
|
||||
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Confirmeu la nova contrasenya",
|
||||
"AuthenticationRequiredUsernameHelpTextWarning": "Introduïu un nom d'usuari nou",
|
||||
"Categories": "Categories",
|
||||
"ApiKeyValidationHealthCheckMessage": "Actualitzeu la vostra clau de l'API perquè tingui almenys {length} caràcters. Podeu fer-ho mitjançant la configuració o el fitxer de configuració",
|
||||
"Episode": "Episodi",
|
||||
"EditApplicationImplementation": "Edita la notificació - {implementationName}",
|
||||
"EditConnectionImplementation": "Afegeix una connexió - {implementationName}",
|
||||
"EditIndexerProxyImplementation": "Edita l'indexador - {implementationName}",
|
||||
"days": "dies",
|
||||
"Album": "àlbum",
|
||||
"Artist": "artista",
|
||||
"AddApplicationImplementation": "Afegeix una condició - {implementationName}",
|
||||
"AddIndexerProxyImplementation": "Afegeix un indexador - {implementationName}",
|
||||
"Category": "Categoria",
|
||||
"Clone": "Clona",
|
||||
"Yes": "Si",
|
||||
"No": "No",
|
||||
"StopSelecting": "Deixa de seleccionar",
|
||||
"External": "Extern",
|
||||
"Id": "ID",
|
||||
"Author": "Autor",
|
||||
"ManageClients": "Gestiona els clients",
|
||||
"CountApplicationsSelected": "{count} Col·lecció(ns) seleccionades"
|
||||
}
|
||||
|
||||
@@ -193,13 +193,13 @@
|
||||
"Restart": "Restartujte",
|
||||
"RestartNow": "Restartovat nyní",
|
||||
"RestoreBackup": "Obnovit zálohu",
|
||||
"RSSIsNotSupportedWithThisIndexer": "RSS není u tohoto indexeru podporováno",
|
||||
"RssIsNotSupportedWithThisIndexer": "RSS není u tohoto indexeru podporováno",
|
||||
"Save": "Uložit",
|
||||
"SSLCertPasswordHelpText": "Heslo pro soubor pfx",
|
||||
"SSLCertPath": "Cesta certifikátu SSL",
|
||||
"SSLCertPathHelpText": "Cesta k souboru pfx",
|
||||
"UnableToLoadBackups": "Nelze načíst zálohy",
|
||||
"UnableToLoadDownloadClients": "Nelze načíst klienty pro stahování",
|
||||
"DownloadClientsLoadError": "Nelze načíst klienty pro stahování",
|
||||
"UnableToLoadGeneralSettings": "Nelze načíst obecná nastavení",
|
||||
"DeleteNotification": "Smazat oznámení",
|
||||
"EnableAutomaticSearch": "Povolit automatické vyhledávání",
|
||||
@@ -244,7 +244,7 @@
|
||||
"CustomFilters": "Vlastní filtry",
|
||||
"Date": "datum",
|
||||
"Dates": "Termíny",
|
||||
"DBMigration": "Migrace databáze",
|
||||
"DatabaseMigration": "Migrace databáze",
|
||||
"Delete": "Vymazat",
|
||||
"DeleteApplicationMessageText": "Opravdu chcete smazat oznámení „{0}“?",
|
||||
"DeleteBackup": "Odstranit zálohu",
|
||||
@@ -288,7 +288,7 @@
|
||||
"PortNumber": "Číslo portu",
|
||||
"Result": "Výsledek",
|
||||
"Retention": "Zadržení",
|
||||
"RSS": "RSS",
|
||||
"Rss": "RSS",
|
||||
"TagsSettingsSummary": "Podívejte se na všechny značky a na to, jak se používají. Nepoužité značky lze odstranit",
|
||||
"TestAll": "Vyzkoušet vše",
|
||||
"TestAllClients": "Vyzkoušejte všechny klienty",
|
||||
@@ -405,5 +405,8 @@
|
||||
"AuthenticationRequiredPasswordHelpTextWarning": "Vložte nové heslo",
|
||||
"AuthenticationRequiredUsernameHelpTextWarning": "Vložte nové uživatelské jméno",
|
||||
"AuthenticationMethodHelpTextWarning": "Prosím vyberte platnou metodu ověřování",
|
||||
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Potvrďte nové heslo"
|
||||
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Potvrďte nové heslo",
|
||||
"days": "dnů",
|
||||
"Id": "ID",
|
||||
"CountApplicationsSelected": "Vybráno {0} kolekcí"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"Disabled": "deaktiveret",
|
||||
"Add": "Tilføj",
|
||||
"AddDownloadClient": "Tilføj downloadklient",
|
||||
"DBMigration": "DB Migration",
|
||||
"DatabaseMigration": "DB Migration",
|
||||
"MIA": "MIA",
|
||||
"ResetAPIKey": "Nulstil API-nøgle",
|
||||
"SettingsTimeFormat": "Tidsformat",
|
||||
@@ -254,8 +254,8 @@
|
||||
"ReleaseStatus": "Frigør status",
|
||||
"Restore": "Gendan",
|
||||
"RestoreBackup": "Gendan sikkerhedskopi",
|
||||
"RSS": "RSS",
|
||||
"RSSIsNotSupportedWithThisIndexer": "RSS understøttes ikke med denne indekseringsenhed",
|
||||
"Rss": "RSS",
|
||||
"RssIsNotSupportedWithThisIndexer": "RSS understøttes ikke med denne indekseringsenhed",
|
||||
"Save": "Gemme",
|
||||
"Scheduled": "Planlagt",
|
||||
"ScriptPath": "Script sti",
|
||||
@@ -264,7 +264,7 @@
|
||||
"SSLPort": "SSL-port",
|
||||
"StartupDirectory": "Startmappe",
|
||||
"Status": "Status",
|
||||
"UnableToLoadDownloadClients": "Kunne ikke indlæse downloadklienter",
|
||||
"DownloadClientsLoadError": "Kunne ikke indlæse downloadklienter",
|
||||
"UpdateCheckStartupTranslocationMessage": "Kan ikke installere opdatering, fordi startmappen '{0}' er i en App Translocation-mappe.",
|
||||
"UpdateMechanismHelpText": "Brug den indbyggede opdateringsfunktion eller et script",
|
||||
"View": "Udsigt",
|
||||
@@ -369,5 +369,11 @@
|
||||
"ResetAPIKeyMessageText": "Er du sikker på, at du vil nulstille din API-nøgle?",
|
||||
"AuthBasic": "Grundlæggende (pop op-browser)",
|
||||
"None": "Ingen",
|
||||
"RestartProwlarr": "Genstart {appName}"
|
||||
"RestartProwlarr": "Genstart {appName}",
|
||||
"AddCustomFilter": "Tilføj tilpasset filter",
|
||||
"AddConnectionImplementation": "Tilføj forbindelse - {implementationName}",
|
||||
"AddConnection": "Tilføj forbindelse",
|
||||
"EditConnectionImplementation": "Tilføj forbindelse - {implementationName}",
|
||||
"AddApplicationImplementation": "Tilføj forbindelse - {implementationName}",
|
||||
"AddIndexerImplementation": "Tilføj betingelse - {implementationName}"
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
"Age": "Alter",
|
||||
"All": "Alle",
|
||||
"AllIndexersHiddenDueToFilter": "Alle Indexer sind durch den ausgewählten Filter ausgeblendet.",
|
||||
"Analytics": "Analytik",
|
||||
"AnalyticsEnabledHelpText": "Sende anonyme Nutzungs- und Fehlerinformationen an die Server von {appName}. Dazu gehören Informationen über Browser, welche Seiten der {appName}-Weboberfläche aufgerufen wurden, Fehlerberichte sowie Betriebssystem- und Laufzeitversion. Wir werden diese Informationen verwenden, um Funktionen und Fehlerbehebungen zu priorisieren.",
|
||||
"Analytics": "Analysen",
|
||||
"AnalyticsEnabledHelpText": "Senden Sie anonyme Nutzungs- und Fehlerinformationen an die Server von {appName}. Dazu gehören Informationen zu Ihrem Browser, welche {appName}-WebUI-Seiten Sie verwenden, Fehlerberichte sowie Betriebssystem- und Laufzeitversion. Wir werden diese Informationen verwenden, um Funktionen und Fehlerbehebungen zu priorisieren.",
|
||||
"ApiKey": "API-Schlüssel",
|
||||
"ApiKeyValidationHealthCheckMessage": "Bitte den API Schlüssel korrigieren, dieser muss mindestens {0} Zeichen lang sein. Die Änderung kann über die Einstellungen oder die Konfigurationsdatei erfolgen",
|
||||
"AppDataDirectory": "AppData Ordner",
|
||||
"AppDataDirectory": "AppData-Verzeichnis",
|
||||
"AppDataLocationHealthCheckMessage": "Ein Update ist nicht möglich, um das Löschen von AppData beim Update zu verhindern",
|
||||
"AppProfileInUse": "App-Profil im Einsatz",
|
||||
"AppProfileSelectHelpText": "App-Profile werden verwendet, um die Einstellungen für RSS, automatische Suche und interaktive Suche bei der Anwendungssynchronisierung zu steuern",
|
||||
@@ -33,54 +33,54 @@
|
||||
"ApplicationStatusCheckSingleClientMessage": "Applikationen wegen folgender Fehler nicht verfügbar: {0}",
|
||||
"Applications": "Anwendungen",
|
||||
"Apply": "Anwenden",
|
||||
"ApplyTags": "Tags setzen",
|
||||
"ApplyTags": "Schlagworte anwenden",
|
||||
"Apps": "Anwendungen",
|
||||
"AudioSearch": "Audio Suche",
|
||||
"Auth": "Authentifizierung",
|
||||
"Authentication": "Authentifizierung",
|
||||
"AuthenticationMethodHelpText": "Für den Zugriff auf {appName} sind Benutzername und Passwort erforderlich",
|
||||
"AuthenticationMethodHelpText": "Für den Zugriff auf {appName} sind Benutzername und Passwort erforderlich.",
|
||||
"Automatic": "Automatisch",
|
||||
"AutomaticSearch": "Automatische Suche",
|
||||
"Backup": "Backups",
|
||||
"BackupFolderHelpText": "Relative Pfade befinden sich unter {appName}s AppData Ordner",
|
||||
"BackupIntervalHelpText": "Intervall zum sichern der Datenbank und Einstellungen",
|
||||
"Backup": "Sicherung",
|
||||
"BackupFolderHelpText": "Relative Pfade befinden sich im AppData-Verzeichnis von {appName}",
|
||||
"BackupIntervalHelpText": "Intervall zwischen automatischen Sicherungen",
|
||||
"BackupNow": "Jetzt sichern",
|
||||
"BackupRetentionHelpText": "Automatische Backups, die älter als die Aufbewahrungsfrist sind, werden automatisch gelöscht",
|
||||
"BackupRetentionHelpText": "Automatische Backups, die älter als der Aufbewahrungszeitraum sind, werden automatisch bereinigt",
|
||||
"Backups": "Sicherungen",
|
||||
"BeforeUpdate": "Vor dem Update",
|
||||
"BindAddress": "Adresse binden",
|
||||
"BindAddressHelpText": "Gültige IP Adresse oder \"*\" für alle Netzwerke",
|
||||
"BindAddressHelpText": "Gültige IP-Adresse, localhost oder „*“ für alle Schnittstellen",
|
||||
"BookSearch": "Buch Suche",
|
||||
"BookSearchTypes": "Buch-Suchtypen",
|
||||
"Branch": "Git-Branch",
|
||||
"Branch": "Branch",
|
||||
"BranchUpdate": "Verwendeter Branch zur Aktualisierung von {appName}",
|
||||
"BranchUpdateMechanism": "Git-Branch für den externen Updateablauf",
|
||||
"BypassProxyForLocalAddresses": "Proxy für lokale Adressen umgehen",
|
||||
"Cancel": "Abbrechen",
|
||||
"CancelPendingTask": "Diese laufende Aufgabe wirklich abbrechen?",
|
||||
"CancelPendingTask": "Möchten Sie diese ausstehende Aufgabe wirklich abbrechen?",
|
||||
"Categories": "Kategorien",
|
||||
"Category": "Kategorie",
|
||||
"CertificateValidation": "Zertifikat Validierung",
|
||||
"CertificateValidation": "Zertifikatsvalidierung",
|
||||
"CertificateValidationHelpText": "Ändere wie streng die Validierung der HTTPS-Zertifizierung ist",
|
||||
"ChangeHasNotBeenSavedYet": "Änderung wurde noch nicht gespeichert",
|
||||
"Clear": "Leeren",
|
||||
"ClearHistory": "Verlauf leeren",
|
||||
"ClearHistoryMessageText": "Wirklich den ganzen {appName} Verlauf löschen?",
|
||||
"ClientPriority": "Priorität",
|
||||
"CloneProfile": "Profil kopieren",
|
||||
"CloneProfile": "Profil klonen",
|
||||
"Close": "Schließen",
|
||||
"CloseCurrentModal": "Momentanes Modal schließen",
|
||||
"Columns": "Spalten",
|
||||
"Component": "Komponente",
|
||||
"Connect": "Benachrichtigungen",
|
||||
"ConnectSettings": "Eintellungen für Verbindungen",
|
||||
"ConnectSettings": "Verbindungseinstellungen",
|
||||
"ConnectSettingsSummary": "Benachrichtigungen und eigene Scripte",
|
||||
"ConnectionLost": "Verbindung unterbrochen",
|
||||
"Connections": "Verbindungen",
|
||||
"CouldNotConnectSignalR": "Es konnte keine Verbindung zu SignalR hergestellt werden, die Benutzeroberfläche wird nicht aktualisiert",
|
||||
"Custom": "Benutzerdefiniert",
|
||||
"CustomFilters": "Eigene Filter",
|
||||
"DBMigration": "DB Migration",
|
||||
"CustomFilters": "Benutzerdefinierte Filter",
|
||||
"DatabaseMigration": "DB Migration",
|
||||
"Database": "Datenbank",
|
||||
"Date": "Datum",
|
||||
"Dates": "Termine",
|
||||
@@ -88,24 +88,24 @@
|
||||
"DeleteAppProfile": "App-Profil löschen",
|
||||
"DeleteApplication": "Applikation löschen",
|
||||
"DeleteApplicationMessageText": "Wirklich die Applikation '{0}' löschen?",
|
||||
"DeleteBackup": "Backup löschen",
|
||||
"DeleteBackupMessageText": "Backup '{0}' wirkich löschen?",
|
||||
"DeleteDownloadClient": "Downloader löschen",
|
||||
"DeleteDownloadClientMessageText": "Downloader '{0}' wirklich löschen?",
|
||||
"DeleteBackup": "Sicherung löschen",
|
||||
"DeleteBackupMessageText": "Sind Sie sicher, dass Sie die Sicherung „{name}“ löschen möchten?",
|
||||
"DeleteDownloadClient": "Download-Client löschen",
|
||||
"DeleteDownloadClientMessageText": "Sind Sie sicher, dass Sie den Download-Client „{name}“ löschen möchten?",
|
||||
"DeleteIndexerProxy": "Indexer Proxy löschen",
|
||||
"DeleteIndexerProxyMessageText": "Tag '{0}' wirklich löschen?",
|
||||
"DeleteNotification": "Benachrichtigung löschen",
|
||||
"DeleteNotificationMessageText": "Benachrichtigung '{0}' wirklich löschen?",
|
||||
"DeleteNotificationMessageText": "Sind Sie sicher, dass Sie die Benachrichtigung „{name}“ löschen möchten?",
|
||||
"DeleteTag": "Tag löschen",
|
||||
"DeleteTagMessageText": "Tag '{0}' wirklich löschen?",
|
||||
"DeleteTagMessageText": "Sind Sie sicher, dass Sie das Tag „{label}“ löschen möchten?",
|
||||
"Description": "Beschreibung",
|
||||
"Details": "Details",
|
||||
"Details": "Einzelheiten",
|
||||
"DevelopmentSettings": "Entwicklungseinstellungen",
|
||||
"Disabled": "Deaktiviert",
|
||||
"Discord": "Discord",
|
||||
"Docker": "Docker",
|
||||
"Donations": "Spenden",
|
||||
"DownloadClient": "Downloader",
|
||||
"DownloadClient": "Download Client",
|
||||
"DownloadClientSettings": "Downloader Einstellungen",
|
||||
"DownloadClientStatusCheckAllClientMessage": "Alle Download Clients sind aufgrund von Fehlern nicht verfügbar",
|
||||
"DownloadClientStatusCheckSingleClientMessage": "Download Clients aufgrund von Fehlern nicht verfügbar: {0}",
|
||||
@@ -293,8 +293,8 @@
|
||||
"QueryResults": "Abfrageergebnisse",
|
||||
"Queue": "Warteschlange",
|
||||
"Queued": "In Warteschlange",
|
||||
"RSS": "RSS",
|
||||
"RSSIsNotSupportedWithThisIndexer": "RSS wird von diesem Indexer nicht unterstützt",
|
||||
"Rss": "RSS",
|
||||
"RssIsNotSupportedWithThisIndexer": "RSS wird von diesem Indexer nicht unterstützt",
|
||||
"RawSearchSupported": "Raw-Suche unterstützt",
|
||||
"ReadTheWikiForMoreInformation": "Lesen Sie das Wiki für weitere Informationen",
|
||||
"Reddit": "Reddit",
|
||||
@@ -307,17 +307,17 @@
|
||||
"Reload": "Neuladen",
|
||||
"Remove": "Entfernen",
|
||||
"RemoveFilter": "Filter entfernen",
|
||||
"RemovedFromTaskQueue": "Aus der Aufgabenwarteschlage entfernt",
|
||||
"RemovedFromTaskQueue": "Aus der Aufgabenwarteschlange entfernt",
|
||||
"RemovingTag": "Tag entfernen",
|
||||
"Replace": "Ersetzen",
|
||||
"Reset": "Zurücksetzen",
|
||||
"ResetAPIKey": "API-Schlüssel zurücksetzen",
|
||||
"Restart": "Neustarten",
|
||||
"Restart": "Neu starten",
|
||||
"RestartNow": "Jetzt neustarten",
|
||||
"RestartProwlarr": "{appName} Neustarten",
|
||||
"RestartRequiredHelpTextWarning": "Erfordert einen Neustart",
|
||||
"RestartRequiredHelpTextWarning": "Erfordert einen Neustart, damit die Aktion wirksam wird",
|
||||
"Restore": "Wiederherstellen",
|
||||
"RestoreBackup": "Backup einspielen",
|
||||
"RestoreBackup": "Sicherung wiederherstellen",
|
||||
"Result": "Ergebnis",
|
||||
"Retention": "Aufbewahrung ( Retention )",
|
||||
"SSLCertPassword": "SSL Zertifikat Passwort",
|
||||
@@ -339,8 +339,8 @@
|
||||
"Seeders": "Seeders",
|
||||
"SelectAll": "Alle wählen",
|
||||
"SemiPrivate": "Halbprivat",
|
||||
"SendAnonymousUsageData": "Anonyme Nutzungsdaten übertragen",
|
||||
"SetTags": "Tags setzen",
|
||||
"SendAnonymousUsageData": "Sende anonyme Nutzungsdaten",
|
||||
"SetTags": "Tags festlegen",
|
||||
"Settings": "Einstellungen",
|
||||
"SettingsConsoleLogLevel": "Konsolen Log Level",
|
||||
"SettingsEnableColorImpairedMode": "Farbbeeinträchtigter Modus aktivieren",
|
||||
@@ -358,7 +358,7 @@
|
||||
"SettingsShowRelativeDatesHelpText": "Relatives (z.B.: Heute, gestern, etc) oder absolutes Datum anzeigen",
|
||||
"SettingsSqlLoggingHelpText": "Log alle SQL Abfragen von {appName}",
|
||||
"SettingsTimeFormat": "Zeitformat",
|
||||
"ShowAdvanced": "Einfache Ansicht",
|
||||
"ShowAdvanced": "Erweitert anzeigen",
|
||||
"ShowSearch": "Suche anzeigen",
|
||||
"ShowSearchHelpText": "Suchbutton anzeigen beim draufzeigen",
|
||||
"Shutdown": "Herunterfahren",
|
||||
@@ -366,7 +366,7 @@
|
||||
"Sort": "Sortieren",
|
||||
"Source": "Quelle",
|
||||
"StartTypingOrSelectAPathBelow": "Eingeben oder unten auswählen",
|
||||
"Started": "gestartet",
|
||||
"Started": "Gestartet",
|
||||
"StartupDirectory": "Start-Verzeichnis",
|
||||
"Stats": "Statistiken",
|
||||
"Status": "Status",
|
||||
@@ -387,13 +387,13 @@
|
||||
"TagIsNotUsedAndCanBeDeleted": "Tag wird nicht benutzt und kann gelöscht werden",
|
||||
"Tags": "Tags",
|
||||
"TagsHelpText": "Wird auf Filme mit mindestens einem passenden Tag angewandt",
|
||||
"TagsSettingsSummary": "Alle Tags und deren Benutzung anzeigen. Unbenutzte Tags können entfernt werden",
|
||||
"TagsSettingsSummary": "Sehen Sie sich alle Tags und deren Verwendung an. Nicht verwendete Tags können entfernt werden",
|
||||
"Tasks": "Aufgaben",
|
||||
"Test": "Testen",
|
||||
"TestAll": "Alle testen",
|
||||
"Test": "Prüfen",
|
||||
"TestAll": "Alle prüfen",
|
||||
"TestAllApps": "Alle Apps testen",
|
||||
"TestAllClients": "Alle testen",
|
||||
"TestAllIndexers": "Alle testen",
|
||||
"TestAllClients": "Prüfe alle Clients",
|
||||
"TestAllIndexers": "Prüfe alle Indexer",
|
||||
"TheLatestVersionIsAlreadyInstalled": "Die aktuellste Version ist bereits installiert",
|
||||
"ThemeHelpText": "Ändere das UI-Theme der Anwendung. Das 'Auto'-Theme verwendet dein Betriebssystem-Theme, um den hellen oder dunklen Modus einzustellen. Inspiriert von {0}",
|
||||
"Time": "Zeit",
|
||||
@@ -418,10 +418,10 @@
|
||||
"UnableToAddANewIndexerProxyPleaseTryAgain": "Der neue Indexer konnte nicht hinzugefügt werden, bitte erneut probieren.",
|
||||
"UnableToAddANewNotificationPleaseTryAgain": "Die neue Benachrichtigung konnte nicht hinzugefügt werden, bitte erneut probieren.",
|
||||
"UnableToLoadAppProfiles": "App-Profile können nicht geladen werden",
|
||||
"UnableToLoadApplicationList": "Anwendungsliste kann nicht geladen werden",
|
||||
"UnableToLoadBackups": "Backups konnten nicht geladen werden",
|
||||
"ApplicationsLoadError": "Anwendungsliste kann nicht geladen werden",
|
||||
"UnableToLoadBackups": "Sicherungen können nicht geladen werden",
|
||||
"UnableToLoadDevelopmentSettings": "Entwicklereinstellungen konnten nicht geladen werden",
|
||||
"UnableToLoadDownloadClients": "Downloader konnten nicht geladen werden",
|
||||
"DownloadClientsLoadError": "Downloader konnten nicht geladen werden",
|
||||
"UnableToLoadGeneralSettings": "Allgemeine Einstellungen konnten nicht geladen werden",
|
||||
"UnableToLoadHistory": "Verlauf konnte nicht geladen werden",
|
||||
"UnableToLoadIndexerProxies": "Indexer-Proxies können nicht geladen werden",
|
||||
@@ -429,41 +429,41 @@
|
||||
"UnableToLoadNotifications": "Benachrichtigungen konnten nicht geladen werden",
|
||||
"UnableToLoadTags": "Tags konnten nicht geladen werden",
|
||||
"UnableToLoadUISettings": "Oberflächen Einstellungen konnten nicht geladen werden",
|
||||
"UnsavedChanges": "Ungespeicherte Änderungen",
|
||||
"UnselectAll": "Keine wählen",
|
||||
"UpdateAutomaticallyHelpText": "Updates automatisch herunteraden und installieren. Es kann weiterhin unter \"System -> Updates\" ein manuelles Update angestoßen werden",
|
||||
"UnsavedChanges": "Nicht gespeicherte Änderungen",
|
||||
"UnselectAll": "Alle abwählen",
|
||||
"UpdateAutomaticallyHelpText": "Updates automatisch herunterladen und installieren. Sie können weiterhin über System: Updates installieren",
|
||||
"UpdateCheckStartupNotWritableMessage": "Update kann nicht installiert werden, da der Startordner '{0}' vom Benutzer '{1}' nicht beschreibbar ist.",
|
||||
"UpdateCheckStartupTranslocationMessage": "Update kann nicht installiert werden, da sich der Startordner '{0}' in einem App Translocation-Ordner befindet.",
|
||||
"UpdateCheckUINotWritableMessage": "Update kann nicht installiert werden, da der Benutzeroberflächenordner '{0}' vom Benutzer '{1}' nicht beschreibbar ist.",
|
||||
"UpdateMechanismHelpText": "Benutze {appName}'s Built-In Updater oder ein Script",
|
||||
"UpdateMechanismHelpText": "Verwenden Sie den integrierten Updater von {appName} oder ein Skript",
|
||||
"UpdateScriptPathHelpText": "Pfad zu einem benutzerdefinierten Skript, das ein extrahiertes Update-Paket übernimmt und den Rest des Update-Prozesses abwickelt",
|
||||
"Updates": "Updates",
|
||||
"Uptime": "Laufzeit",
|
||||
"Updates": "Aktualisierung",
|
||||
"Uptime": "Betriebszeit",
|
||||
"Url": "Url",
|
||||
"UrlBaseHelpText": "Für Reverse-Proxy-Unterstützung. Die Standardeinstellung leer",
|
||||
"UseProxy": "Proxy benutzen",
|
||||
"UrlBaseHelpText": "Für die Reverse-Proxy-Unterstützung ist der Standardwert leer",
|
||||
"UseProxy": "Verwende Proxy",
|
||||
"Usenet": "Usenet",
|
||||
"UserAgentProvidedByTheAppThatCalledTheAPI": "UserAgent von der App welcher die API aufgerufen hat",
|
||||
"Username": "Benutzername",
|
||||
"Username": "Nutzername",
|
||||
"Version": "Version",
|
||||
"View": "Ansicht",
|
||||
"Warn": "Warnung",
|
||||
"Warn": "Achtung",
|
||||
"Website": "Webseite",
|
||||
"Wiki": "Wiki",
|
||||
"Yes": "Ja",
|
||||
"YesCancel": "Ja, abbrechen",
|
||||
"YesCancel": "Ja Abbrechen",
|
||||
"Yesterday": "Gestern",
|
||||
"OnHealthRestoredHelpText": "Bei Wiederherstellung des Zustands",
|
||||
"OnHealthRestored": "Bei Wiederherstellung des Zustands",
|
||||
"StopSelecting": "Auswahl stoppen",
|
||||
"ApplicationURL": "Anwendungs-URL",
|
||||
"ApplicationUrlHelpText": "Die externe URL der Anwendung inklusive http(s)://, Port und URL-Basis",
|
||||
"ApplicationUrlHelpText": "Die externe URL dieser Anwendung, einschließlich http(s)://, Port und URL-Basis",
|
||||
"ApplyChanges": "Änderungen anwenden",
|
||||
"CountIndexersSelected": "{0} Indexer ausgewählt",
|
||||
"CountIndexersSelected": "{count} Indexer ausgewählt",
|
||||
"DeleteSelectedDownloadClients": "Lösche Download Client(s)",
|
||||
"DeleteSelectedApplicationsMessageText": "Indexer '{0}' wirklich löschen?",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Indexer '{0}' wirklich löschen?",
|
||||
"DeleteSelectedIndexersMessageText": "Indexer '{0}' wirklich löschen?",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Sind Sie sicher, dass Sie {count} ausgewählte Download-Clients löschen möchten?",
|
||||
"DeleteSelectedIndexersMessageText": "Sind Sie sicher, dass Sie {count} ausgewählte(n) Indexer löschen möchten?",
|
||||
"EditSelectedDownloadClients": "Ausgewählte Download Clienten bearbeiten",
|
||||
"Implementation": "Integration",
|
||||
"ManageDownloadClients": "Verwalte Download Clienten",
|
||||
@@ -471,11 +471,11 @@
|
||||
"NoIndexersFound": "Keine Indexer gefunden",
|
||||
"Theme": "Design",
|
||||
"Season": "Staffel",
|
||||
"ApplyTagsHelpTextAdd": "Hinzufügen: Füge neu Tags zu den existierenden Tags hinzu",
|
||||
"ApplyTagsHelpTextAdd": "Hinzufügen: Fügen Sie die Tags der vorhandenen Tag-Liste hinzu",
|
||||
"ApplyTagsHelpTextHowToApplyApplications": "Wie werden Tags zu ausgewählten Autoren zugeteilt",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Wie werden Tags zu ausgewählten Indexern zugeteilt",
|
||||
"ApplyTagsHelpTextRemove": "Entfernen: Eingegebene Tags entfernen",
|
||||
"ApplyTagsHelpTextReplace": "Ersetzen: Nur eingegebene Tags übernehmen und vorhandene entfernen( keine Tags eingeben um alle zu entfernen )",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "So wenden Sie Tags auf die ausgewählten Indexer an",
|
||||
"ApplyTagsHelpTextRemove": "Entfernen: Die eingegebenen Tags entfernen",
|
||||
"ApplyTagsHelpTextReplace": "Ersetzen: Ersetzen Sie die Tags durch die eingegebenen Tags (geben Sie keine Tags ein, um alle Tags zu löschen).",
|
||||
"DownloadClientPriorityHelpText": "Priorisiere mehrere Downloader. Rundlauf-Verfahren wird für Downloader mit der gleichen Priorität verwendet.",
|
||||
"EditSelectedIndexers": "Ausgewähle Indexer bearbeiten",
|
||||
"SelectIndexers": "Indexer suchen",
|
||||
@@ -491,20 +491,20 @@
|
||||
"Artist": "Künstler",
|
||||
"Author": "Autor",
|
||||
"Book": "Buch",
|
||||
"ConnectionLostReconnect": "Radarr wird automatisch versuchen zu verbinden oder klicke unten auf neuladen.",
|
||||
"ConnectionLostToBackend": "Radarr hat die Verbindung zum Backend verloren und muss neugeladen werden.",
|
||||
"ConnectionLostReconnect": "{appName} wird versuchen, automatisch eine Verbindung herzustellen, oder Sie können unten auf „Neu laden“ klicken.",
|
||||
"ConnectionLostToBackend": "{appName} hat die Verbindung zum Backend verloren und muss neu geladen werden, um die Funktionalität wiederherzustellen.",
|
||||
"RecentChanges": "Kürzliche Änderungen",
|
||||
"WhatsNew": "Was gibt's Neues?",
|
||||
"WhatsNew": "Was ist neu?",
|
||||
"minutes": "Minuten",
|
||||
"DeleteAppProfileMessageText": "Qualitätsprofil '{0}' wirklich löschen?",
|
||||
"AddConnection": "Verbindung hinzufügen",
|
||||
"NotificationStatusAllClientHealthCheckMessage": "Wegen Fehlern sind keine Applikationen verfügbar",
|
||||
"NotificationStatusSingleClientHealthCheckMessage": "Applikationen wegen folgender Fehler nicht verfügbar: {0}",
|
||||
"AuthBasic": "Einfach (Browser Popup)",
|
||||
"AuthForm": "Formular (Login Seite)",
|
||||
"DisabledForLocalAddresses": "Für Lokale Adressen deaktivieren",
|
||||
"AuthBasic": "Basis (Browser-Popup)",
|
||||
"AuthForm": "Formulare (Anmeldeseite)",
|
||||
"DisabledForLocalAddresses": "Für lokale Adressen deaktiviert",
|
||||
"None": "Keine",
|
||||
"ResetAPIKeyMessageText": "Bist du sicher, dass du den API-Schlüssel zurücksetzen willst?",
|
||||
"ResetAPIKeyMessageText": "Sind Sie sicher, dass Sie Ihren API-Schlüssel zurücksetzen möchten?",
|
||||
"AddCustomFilter": "Eigenen Filter hinzufügen",
|
||||
"AddApplication": "Application hinzufügen",
|
||||
"AddCategory": "Kategorie hinzufügen",
|
||||
@@ -532,5 +532,18 @@
|
||||
"AuthenticationRequiredUsernameHelpTextWarning": "Gib einen neuen Benutzernamen ein",
|
||||
"AuthenticationMethodHelpTextWarning": "Bitte wähle eine gültige Authentifizierungsmethode aus",
|
||||
"AuthenticationRequiredPasswordHelpTextWarning": "Gib ein neues Passwort ein",
|
||||
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Neues Passwort bestätigen"
|
||||
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Neues Passwort bestätigen",
|
||||
"DefaultNameCopiedProfile": "{Name} – Kopieren",
|
||||
"AuthenticationMethod": "Authentifizierungsmethode",
|
||||
"Clone": "Klonen",
|
||||
"CountDownloadClientsSelected": "{count} Download-Client(s) ausgewählt",
|
||||
"EditConnectionImplementation": "Verbindung hinzufügen - {implementationName}",
|
||||
"EditDownloadClientImplementation": "Download-Client hinzufügen - {implementationName}",
|
||||
"IndexerTagsHelpTextWarning": "Tags sollten mit Vorsicht verwendet werden, da sie ungewollte Effekte haben können. Eine Anwendung mit einem Tag synchronisiert nur Indexer die den Gleichen Tag haben.",
|
||||
"EditIndexerImplementation": "Indexer hinzufügen - {implementationName}",
|
||||
"EditApplicationImplementation": "Anwendung hinzufügen - {implementationName}",
|
||||
"EditIndexerProxyImplementation": "Indexer Proxy hinzufügen - {implementationName}",
|
||||
"CountApplicationsSelected": "{count} Ausgewählte Sammlung(en)",
|
||||
"DownloadClientAriaSettingsDirectoryHelpText": "Optionaler Speicherort für Downloads. Lassen Sie das Feld leer, um den standardmäßigen rTorrent-Speicherort zu verwenden",
|
||||
"ManageClients": "Verwalte Clienten"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user