mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-09 15:01:39 -04:00
Compare commits
2 Commits
postgres-t
...
list-exclu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74382d7250 | ||
|
|
6659bc034c |
@@ -576,7 +576,6 @@ stages:
|
||||
-e POSTGRES_PASSWORD=radarr \
|
||||
-e POSTGRES_USER=radarr \
|
||||
-p 5432:5432/tcp \
|
||||
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
||||
postgres:14
|
||||
displayName: Start postgres
|
||||
- bash: |
|
||||
@@ -722,7 +721,6 @@ stages:
|
||||
-e POSTGRES_PASSWORD=radarr \
|
||||
-e POSTGRES_USER=radarr \
|
||||
-p 5432:5432/tcp \
|
||||
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
||||
postgres:14
|
||||
displayName: Start postgres
|
||||
- bash: |
|
||||
|
||||
@@ -181,12 +181,13 @@ class Blocklist extends Component {
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
items.map((item, index) => {
|
||||
return (
|
||||
<BlocklistRowConnector
|
||||
key={item.id}
|
||||
isSelected={selectedState[item.id] || false}
|
||||
columns={columns}
|
||||
index={index}
|
||||
{...item}
|
||||
onSelectedChange={this.onSelectedChange}
|
||||
/>
|
||||
|
||||
@@ -66,8 +66,7 @@ class CollectionFooter extends Component {
|
||||
monitor,
|
||||
monitored,
|
||||
qualityProfileId,
|
||||
minimumAvailability,
|
||||
rootFolderPath
|
||||
minimumAvailability
|
||||
} = this.state;
|
||||
|
||||
const changes = {};
|
||||
@@ -88,10 +87,6 @@ class CollectionFooter extends Component {
|
||||
changes.minimumAvailability = minimumAvailability;
|
||||
}
|
||||
|
||||
if (rootFolderPath !== NO_CHANGE) {
|
||||
changes.rootFolderPath = rootFolderPath;
|
||||
}
|
||||
|
||||
this.props.onUpdateSelectedPress(changes);
|
||||
};
|
||||
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
|
||||
.tmdbId,
|
||||
.movieYear {
|
||||
flex: 0 0 70px;
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex: 1 0 auto;
|
||||
padding-right: 10px;
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import Link from 'Components/Link/Link';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
|
||||
import styles from './ImportListExclusion.css';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
|
||||
class ImportListExclusion extends Component {
|
||||
|
||||
@@ -55,28 +58,82 @@ class ImportListExclusion extends Component {
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
isSelected,
|
||||
onSelectedChange,
|
||||
columns,
|
||||
movieTitle,
|
||||
tmdbId,
|
||||
movieYear
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
styles.importExclusion
|
||||
)}
|
||||
>
|
||||
<div className={styles.tmdbId}>{tmdbId}</div>
|
||||
<div className={styles.movieTitle}>{movieTitle}</div>
|
||||
<div className={styles.movieYear}>{movieYear}</div>
|
||||
<TableRow>
|
||||
<TableSelectCell
|
||||
id={id}
|
||||
isSelected={isSelected}
|
||||
onSelectedChange={onSelectedChange}
|
||||
/>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<Link
|
||||
onPress={this.onEditImportExclusionPress}
|
||||
>
|
||||
<Icon name={icons.EDIT} />
|
||||
</Link>
|
||||
</div>
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
name,
|
||||
isVisible
|
||||
} = column;
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name === 'tmdbId') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{tmdbId}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'movieTitle') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{movieTitle}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'movieYear') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{movieYear}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'actions') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.actions}
|
||||
>
|
||||
<IconButton
|
||||
title={translate('RemoveFromBlocklist')}
|
||||
name={icons.EDIT}
|
||||
onPress={this.onEditImportExclusionPress}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
title={translate('RemoveFromBlocklist')}
|
||||
name={icons.REMOVE}
|
||||
kind={kinds.DANGER}
|
||||
onPress={this.onDeleteImportExclusionPress}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
}
|
||||
|
||||
<EditImportListExclusionModalConnector
|
||||
id={id}
|
||||
@@ -94,7 +151,7 @@ class ImportListExclusion extends Component {
|
||||
onConfirm={this.onConfirmDeleteImportExclusion}
|
||||
onCancel={this.onDeleteImportExclusionModalClose}
|
||||
/>
|
||||
</div>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -104,6 +161,9 @@ ImportListExclusion.propTypes = {
|
||||
movieTitle: PropTypes.string.isRequired,
|
||||
tmdbId: PropTypes.number.isRequired,
|
||||
movieYear: PropTypes.number.isRequired,
|
||||
isSelected: PropTypes.bool.isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onSelectedChange: PropTypes.func.isRequired,
|
||||
onConfirmDeleteImportExclusion: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -4,8 +4,12 @@ import FieldSet from 'Components/FieldSet';
|
||||
import Icon from 'Components/Icon';
|
||||
import Link from 'Components/Link/Link';
|
||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
|
||||
import ImportListExclusion from './ImportListExclusion';
|
||||
import styles from './ImportListExclusions.css';
|
||||
@@ -19,6 +23,10 @@ class ImportListExclusions extends Component {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
allSelected: false,
|
||||
allUnselected: false,
|
||||
lastToggled: null,
|
||||
selectedState: {},
|
||||
isAddImportExclusionModalOpen: false
|
||||
};
|
||||
}
|
||||
@@ -26,6 +34,16 @@ class ImportListExclusions extends Component {
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onSelectAllChange = ({ value }) => {
|
||||
this.setState(selectAll(this.state.selectedState, value));
|
||||
};
|
||||
|
||||
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
||||
this.setState((state) => {
|
||||
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
||||
});
|
||||
};
|
||||
|
||||
onAddImportExclusionPress = () => {
|
||||
this.setState({ isAddImportExclusionModalOpen: true });
|
||||
};
|
||||
@@ -41,41 +59,50 @@ class ImportListExclusions extends Component {
|
||||
const {
|
||||
items,
|
||||
onConfirmDeleteImportExclusion,
|
||||
columns,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
allSelected,
|
||||
allUnselected,
|
||||
selectedState
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<FieldSet legend={translate('ListExclusions')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('UnableToLoadListExclusions')}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.importListExclusionsHeader}>
|
||||
<div className={styles.tmdbId}>
|
||||
TMDb Id
|
||||
</div>
|
||||
<div className={styles.title}>
|
||||
{translate('Title')}
|
||||
</div>
|
||||
<div className={styles.movieYear}>
|
||||
{translate('Year')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{
|
||||
items.map((item, index) => {
|
||||
return (
|
||||
<ImportListExclusion
|
||||
key={item.id}
|
||||
{...item}
|
||||
{...otherProps}
|
||||
index={index}
|
||||
onConfirmDeleteImportExclusion={onConfirmDeleteImportExclusion}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
<Table
|
||||
selectAll={true}
|
||||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
columns={columns}
|
||||
{...otherProps}
|
||||
onSelectAllChange={this.onSelectAllChange}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item, index) => {
|
||||
return (
|
||||
<ImportListExclusion
|
||||
key={item.id}
|
||||
isSelected={selectedState[item.id] || false}
|
||||
{...item}
|
||||
{...otherProps}
|
||||
columns={columns}
|
||||
index={index}
|
||||
onSelectedChange={this.onSelectedChange}
|
||||
onConfirmDeleteImportExclusion={onConfirmDeleteImportExclusion}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<div className={styles.addImportExclusion}>
|
||||
@@ -101,6 +128,7 @@ class ImportListExclusions extends Component {
|
||||
ImportListExclusions.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onConfirmDeleteImportExclusion: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHand
|
||||
import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
|
||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||
import { createThunk } from 'Store/thunks';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
//
|
||||
// Variables
|
||||
@@ -48,7 +49,34 @@ export default {
|
||||
items: [],
|
||||
isSaving: false,
|
||||
saveError: null,
|
||||
pendingChanges: {}
|
||||
pendingChanges: {},
|
||||
|
||||
columns: [
|
||||
{
|
||||
name: 'tmdbId',
|
||||
label: 'TmdbId',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'movieTitle',
|
||||
label: translate('Title'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'movieYear',
|
||||
label: translate('Year'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
columnLabel: translate('Actions'),
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
//
|
||||
|
||||
@@ -561,7 +561,7 @@ export const actionHandlers = handleThunks({
|
||||
}, []);
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/importlist/movie',
|
||||
url: '/movie/import',
|
||||
method: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(allNewMovies)
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import _ from 'lodash';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import getNewMovie from 'Utilities/Movie/getNewMovie';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { set, update, updateItem } from './baseActions';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import createSaveProviderHandler from './Creators/createSaveProviderHandler';
|
||||
@@ -65,81 +63,19 @@ export const defaultState = {
|
||||
}
|
||||
],
|
||||
|
||||
filterPredicates: {
|
||||
genres: function(item, filterValue, type) {
|
||||
const predicate = filterTypePredicates[type];
|
||||
|
||||
let allGenres = [];
|
||||
item.movies.forEach((movie) => {
|
||||
allGenres = allGenres.concat(movie.genres);
|
||||
});
|
||||
|
||||
const genres = Array.from(new Set(allGenres)).slice(0, 3);
|
||||
|
||||
return predicate(genres, filterValue);
|
||||
},
|
||||
totalMovies: function(item, filterValue, type) {
|
||||
const predicate = filterTypePredicates[type];
|
||||
const { movies } = item;
|
||||
|
||||
const totalMovies = movies.length;
|
||||
return predicate(totalMovies, filterValue);
|
||||
}
|
||||
},
|
||||
filterPredicates: {},
|
||||
|
||||
filterBuilderProps: [
|
||||
{
|
||||
name: 'title',
|
||||
label: translate('Title'),
|
||||
label: 'Title',
|
||||
type: filterBuilderTypes.STRING
|
||||
},
|
||||
{
|
||||
name: 'monitored',
|
||||
label: translate('Monitored'),
|
||||
label: 'Monitored',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.BOOL
|
||||
},
|
||||
{
|
||||
name: 'qualityProfileId',
|
||||
label: translate('QualityProfile'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.QUALITY_PROFILE
|
||||
},
|
||||
{
|
||||
name: 'rootFolderPath',
|
||||
label: translate('RootFolder'),
|
||||
type: filterBuilderTypes.STRING
|
||||
},
|
||||
{
|
||||
name: 'genres',
|
||||
label: translate('Genres'),
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
optionsSelector: function(items) {
|
||||
const genreList = items.reduce((acc, collection) => {
|
||||
let collectionGenres = [];
|
||||
collection.movies.forEach((movie) => {
|
||||
collectionGenres = collectionGenres.concat(movie.genres);
|
||||
});
|
||||
|
||||
const genres = Array.from(new Set(collectionGenres)).slice(0, 3);
|
||||
|
||||
genres.forEach((genre) => {
|
||||
acc.push({
|
||||
id: genre,
|
||||
name: genre
|
||||
});
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return genreList.sort(sortByName);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'totalMovies',
|
||||
label: translate('TotalMovies'),
|
||||
type: filterBuilderTypes.NUMBER
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -11,7 +11,6 @@ using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Movies.Collections;
|
||||
using NzbDrone.Core.Movies.Commands;
|
||||
using NzbDrone.Core.Movies.Credits;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
@@ -53,10 +52,6 @@ namespace NzbDrone.Core.Test.MovieTests
|
||||
Mocker.GetMock<IProvideMovieInfo>()
|
||||
.Setup(s => s.GetMovieInfo(It.IsAny<int>()))
|
||||
.Callback<int>((i) => { throw new MovieNotFoundException(i); });
|
||||
|
||||
Mocker.GetMock<IRootFolderService>()
|
||||
.Setup(s => s.GetBestRootFolderPath(It.IsAny<string>()))
|
||||
.Returns(string.Empty);
|
||||
}
|
||||
|
||||
private void GivenNewMovieInfo(MovieMetadata movie)
|
||||
|
||||
@@ -41,14 +41,6 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
ParseAndVerifyQuality(title, Source.TELESYNC, proper, Resolution.R720p);
|
||||
}
|
||||
|
||||
[TestCase("Movie Name 2018 NEW PROPER 720p HD-CAM X264 HQ-CPG", true)]
|
||||
[TestCase("Movie Name (2022) 1080p HQCAM ENG x264 AAC - QRips", false)]
|
||||
[TestCase("Movie Name (2018) 720p Hindi HQ CAMrip x264 AAC 1.4GB", false)]
|
||||
public void should_parse_cam(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Source.CAM, proper, Resolution.Unknown);
|
||||
}
|
||||
|
||||
[TestCase("S07E23 .avi ", false)]
|
||||
[TestCase("Movie Name S02E01 HDTV XviD 2HD", false)]
|
||||
[TestCase("Movie Name S05E11 PROPER HDTV XviD 2HD", true)]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
@@ -19,8 +18,6 @@ namespace NzbDrone.Core.CustomFormats
|
||||
return (ICustomFormatSpecification)MemberwiseClone();
|
||||
}
|
||||
|
||||
public abstract NzbDroneValidationResult Validate();
|
||||
|
||||
public bool IsSatisfiedBy(ParsedMovieInfo movieInfo)
|
||||
{
|
||||
var match = IsSatisfiedByWithoutNegate(movieInfo);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
@@ -12,8 +11,6 @@ namespace NzbDrone.Core.CustomFormats
|
||||
bool Negate { get; set; }
|
||||
bool Required { get; set; }
|
||||
|
||||
NzbDroneValidationResult Validate();
|
||||
|
||||
ICustomFormatSpecification Clone();
|
||||
bool IsSatisfiedBy(ParsedMovieInfo movieInfo);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class IndexerFlagSpecificationValidator : AbstractValidator<IndexerFlagSpecification>
|
||||
{
|
||||
public IndexerFlagSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Value).NotEmpty();
|
||||
RuleFor(c => c.Value).Custom((qualityValue, context) =>
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(IndexerFlags), qualityValue))
|
||||
{
|
||||
context.AddFailure(string.Format("Invalid indexer flag condition value: {0}", qualityValue));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class IndexerFlagSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly IndexerFlagSpecificationValidator Validator = new IndexerFlagSpecificationValidator();
|
||||
|
||||
public override int Order => 4;
|
||||
public override string ImplementationName => "Indexer Flag";
|
||||
|
||||
@@ -37,10 +17,5 @@ namespace NzbDrone.Core.CustomFormats
|
||||
var flags = movieInfo?.ExtraInfo?.GetValueOrDefault("IndexerFlags") as IndexerFlags?;
|
||||
return flags?.HasFlag((IndexerFlags)Value) == true;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,11 @@
|
||||
using System.Linq;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class LanguageSpecificationValidator : AbstractValidator<LanguageSpecification>
|
||||
{
|
||||
public LanguageSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Value).NotEmpty();
|
||||
RuleFor(c => c.Value).Custom((value, context) =>
|
||||
{
|
||||
if (!Language.All.Any(o => o.Id == value))
|
||||
{
|
||||
context.AddFailure(string.Format("Invalid Language condition value: {0}", value));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class LanguageSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly LanguageSpecificationValidator Validator = new LanguageSpecificationValidator();
|
||||
|
||||
public override int Order => 3;
|
||||
public override string ImplementationName => "Language";
|
||||
|
||||
@@ -39,10 +19,5 @@ namespace NzbDrone.Core.CustomFormats
|
||||
: (Language)Value;
|
||||
return movieInfo?.Languages?.Contains(comparedLanguage) ?? false;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,11 @@
|
||||
using System;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class QualityModifierSpecificationValidator : AbstractValidator<QualityModifierSpecification>
|
||||
{
|
||||
public QualityModifierSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Value).NotEmpty();
|
||||
RuleFor(c => c.Value).Custom((qualityValue, context) =>
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(Modifier), qualityValue))
|
||||
{
|
||||
context.AddFailure(string.Format("Invalid quality modifier condition value: {0}", qualityValue));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class QualityModifierSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly QualityModifierSpecificationValidator Validator = new QualityModifierSpecificationValidator();
|
||||
|
||||
public override int Order => 7;
|
||||
public override string ImplementationName => "Quality Modifier";
|
||||
|
||||
@@ -36,10 +16,5 @@ namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
return (movieInfo?.Quality?.Quality?.Modifier ?? (int)Modifier.NONE) == (Modifier)Value;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,10 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class RegexSpecificationBaseValidator : AbstractValidator<RegexSpecificationBase>
|
||||
{
|
||||
public RegexSpecificationBaseValidator()
|
||||
{
|
||||
RuleFor(c => c.Value).NotEmpty().WithMessage("Regex Pattern must not be empty");
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class RegexSpecificationBase : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly RegexSpecificationBaseValidator Validator = new RegexSpecificationBaseValidator();
|
||||
|
||||
protected Regex _regex;
|
||||
protected string _raw;
|
||||
|
||||
@@ -28,11 +15,7 @@ namespace NzbDrone.Core.CustomFormats
|
||||
set
|
||||
{
|
||||
_raw = value;
|
||||
|
||||
if (value.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
}
|
||||
_regex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,10 +28,5 @@ namespace NzbDrone.Core.CustomFormats
|
||||
|
||||
return _regex.IsMatch(compared);
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,11 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class ResolutionSpecificationValidator : AbstractValidator<ResolutionSpecification>
|
||||
{
|
||||
public ResolutionSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Value).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class ResolutionSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly ResolutionSpecificationValidator Validator = new ResolutionSpecificationValidator();
|
||||
|
||||
public override int Order => 6;
|
||||
public override string ImplementationName => "Resolution";
|
||||
|
||||
@@ -28,10 +16,5 @@ namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
return (movieInfo?.Quality?.Quality?.Resolution ?? (int)Resolution.Unknown) == Value;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class SizeSpecificationValidator : AbstractValidator<SizeSpecification>
|
||||
{
|
||||
public SizeSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Min).GreaterThan(0);
|
||||
RuleFor(c => c.Max).GreaterThan(c => c.Min);
|
||||
}
|
||||
}
|
||||
|
||||
public class SizeSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly SizeSpecificationValidator Validator = new SizeSpecificationValidator();
|
||||
|
||||
public override int Order => 8;
|
||||
public override string ImplementationName => "Size";
|
||||
|
||||
@@ -34,10 +21,5 @@ namespace NzbDrone.Core.CustomFormats
|
||||
|
||||
return size > Min.Gigabytes() && size <= Max.Gigabytes();
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,11 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
public class SourceSpecificationValidator : AbstractValidator<SourceSpecification>
|
||||
{
|
||||
public SourceSpecificationValidator()
|
||||
{
|
||||
RuleFor(c => c.Value).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class SourceSpecification : CustomFormatSpecificationBase
|
||||
{
|
||||
private static readonly SourceSpecificationValidator Validator = new SourceSpecificationValidator();
|
||||
|
||||
public override int Order => 5;
|
||||
public override string ImplementationName => "Source";
|
||||
|
||||
@@ -28,10 +16,5 @@ namespace NzbDrone.Core.CustomFormats
|
||||
{
|
||||
return (movieInfo?.Quality?.Quality?.Source ?? (int)Source.UNKNOWN) == (Source)Value;
|
||||
}
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
SortTitle = Parser.Parser.NormalizeTitle(collectionName),
|
||||
Added = added,
|
||||
QualityProfileId = qualityProfileId,
|
||||
RootFolderPath = rootFolderPath.TrimEnd('/', '\\', ' '),
|
||||
RootFolderPath = rootFolderPath,
|
||||
SearchOnAdd = true,
|
||||
MinimumAvailability = minimumAvailability
|
||||
});
|
||||
|
||||
@@ -23,7 +23,6 @@ namespace NzbDrone.Core.ImportLists
|
||||
private readonly IImportListStatusService _importListStatusService;
|
||||
private readonly IImportListMovieService _listMovieService;
|
||||
private readonly ISearchForNewMovie _movieSearch;
|
||||
private readonly IProvideMovieInfo _movieInfoService;
|
||||
private readonly IMovieMetadataService _movieMetadataService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -31,7 +30,6 @@ namespace NzbDrone.Core.ImportLists
|
||||
IImportListStatusService importListStatusService,
|
||||
IImportListMovieService listMovieService,
|
||||
ISearchForNewMovie movieSearch,
|
||||
IProvideMovieInfo movieInfoService,
|
||||
IMovieMetadataService movieMetadataService,
|
||||
Logger logger)
|
||||
{
|
||||
@@ -39,7 +37,6 @@ namespace NzbDrone.Core.ImportLists
|
||||
_importListStatusService = importListStatusService;
|
||||
_listMovieService = listMovieService;
|
||||
_movieSearch = movieSearch;
|
||||
_movieInfoService = movieInfoService;
|
||||
_movieMetadataService = movieMetadataService;
|
||||
_logger = logger;
|
||||
}
|
||||
@@ -87,10 +84,20 @@ namespace NzbDrone.Core.ImportLists
|
||||
|
||||
if (!importListReports.AnyFailure)
|
||||
{
|
||||
var alreadyMapped = result.Movies.Where(x => importListReports.Movies.Any(r => r.TmdbId == x.TmdbId));
|
||||
var listMovies = MapMovieReports(importListReports.Movies.Where(x => !result.Movies.Any(r => r.TmdbId == x.TmdbId)).ToList()).Where(x => x.TmdbId > 0).ToList();
|
||||
// TODO some opportunity to bulk map here if we had the tmdbIds
|
||||
var listMovies = importListReports.Movies.Select(x =>
|
||||
{
|
||||
// Is it existing in result
|
||||
var movie = result.Movies.FirstOrDefault(r => r.TmdbId == x.TmdbId);
|
||||
|
||||
if (movie != null)
|
||||
{
|
||||
movie.ListId = importList.Definition.Id;
|
||||
}
|
||||
|
||||
return movie ?? MapMovieReport(x);
|
||||
}).Where(x => x.TmdbId > 0).ToList();
|
||||
|
||||
listMovies.AddRange(alreadyMapped);
|
||||
listMovies = listMovies.DistinctBy(x => x.TmdbId).ToList();
|
||||
listMovies.ForEach(m => m.ListId = importList.Definition.Id);
|
||||
|
||||
@@ -141,10 +148,13 @@ namespace NzbDrone.Core.ImportLists
|
||||
|
||||
if (!importListReports.AnyFailure)
|
||||
{
|
||||
var listMovies = MapMovieReports(importListReports.Movies).Where(x => x.TmdbId > 0).ToList();
|
||||
// TODO some opportunity to bulk map here if we had the tmdbIds
|
||||
var listMovies = importListReports.Movies.Select(x =>
|
||||
{
|
||||
return MapMovieReport(x);
|
||||
}).Where(x => x.TmdbId > 0).ToList();
|
||||
|
||||
listMovies = listMovies.DistinctBy(x => x.TmdbId).ToList();
|
||||
listMovies.ForEach(m => m.ListId = importList.Definition.Id);
|
||||
|
||||
result.Movies.AddRange(listMovies);
|
||||
_listMovieService.SyncMoviesForList(listMovies, importList.Definition.Id);
|
||||
@@ -163,30 +173,21 @@ namespace NzbDrone.Core.ImportLists
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<ImportListMovie> MapMovieReports(List<ImportListMovie> reports)
|
||||
private ImportListMovie MapMovieReport(ImportListMovie report)
|
||||
{
|
||||
var mappedMovies = reports.Select(m => _movieSearch.MapMovieToTmdbMovie(new MovieMetadata { Title = m.Title, TmdbId = m.TmdbId, ImdbId = m.ImdbId, Year = m.Year }))
|
||||
.Where(x => x != null)
|
||||
.ToList();
|
||||
var mappedMovie = _movieSearch.MapMovieToTmdbMovie(new MovieMetadata { Title = report.Title, TmdbId = report.TmdbId, ImdbId = report.ImdbId, Year = report.Year });
|
||||
|
||||
_movieMetadataService.UpsertMany(mappedMovies);
|
||||
var mappedListMovie = new ImportListMovie { ListId = report.ListId };
|
||||
|
||||
var mappedListMovies = new List<ImportListMovie>();
|
||||
|
||||
foreach (var movieMeta in mappedMovies)
|
||||
if (mappedMovie != null)
|
||||
{
|
||||
var mappedListMovie = new ImportListMovie();
|
||||
_movieMetadataService.Upsert(mappedMovie);
|
||||
|
||||
if (movieMeta != null)
|
||||
{
|
||||
mappedListMovie.MovieMetadata = movieMeta;
|
||||
mappedListMovie.MovieMetadataId = movieMeta.Id;
|
||||
}
|
||||
|
||||
mappedListMovies.Add(mappedListMovie);
|
||||
mappedListMovie.MovieMetadata = mappedMovie;
|
||||
mappedListMovie.MovieMetadataId = mappedMovie.Id;
|
||||
}
|
||||
|
||||
return mappedListMovies;
|
||||
return mappedListMovie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
"AlternativeTitle": "Alternative Titel",
|
||||
"AllMoviesHiddenDueToFilter": "Alle Filme sind wegen dem Filter ausgeblendet.",
|
||||
"Age": "Alter",
|
||||
"AddNewMovie": "Neuen Film hinzufügen",
|
||||
"AddNewMovie": "Neuer Film...",
|
||||
"AddList": "Liste hinzufügen",
|
||||
"SystemTimeCheckMessage": "Die Systemzeit ist um einen Tag versetzt. Bis die Zeit korrigiert wurde, könnten die geplanten Aufgaben nicht korrekt ausgeführt werden",
|
||||
"UnsavedChanges": "Ungespeicherte Änderungen",
|
||||
@@ -640,7 +640,7 @@
|
||||
"RemovingTag": "Tag entfernen",
|
||||
"ReleaseWillBeProcessedInterp": "Release wird verarbeitet {0}",
|
||||
"Queued": "Eingereiht",
|
||||
"QualityProfileDeleteConfirm": "Qualitätsprofil '{0}' wirklich löschen",
|
||||
"QualityProfileDeleteConfirm": "Qualitätsprofil '{0}' wirklich löschen?",
|
||||
"Pending": "Ausstehend",
|
||||
"Paused": "Pausiert",
|
||||
"NegateHelpText": "Wenn aktiviert wird das eigene Format nicht angewendet solange diese {0} Bedingung zutrifft.",
|
||||
@@ -1047,7 +1047,7 @@
|
||||
"RemotePathMappingCheckFilesLocalWrongOSPath": "Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.",
|
||||
"RemotePathMappingCheckFilesBadDockerPath": "Docker erkannt; Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe deine Remote-Pfadzuordnungen und die Downloader Einstellungen.",
|
||||
"RemotePathMappingCheckFilesWrongOSPath": "Downloader {0} meldet Dateien in {1}, aber dies ist kein valider {2} Pfad. Überprüfe deine Remote-Pfadzuordnungen und die Downloader Einstellungen.",
|
||||
"RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Radarr kann dieses Verzeichnis nicht sehen. Möglicherweise müssen die Verzeichnisrechte angepasst werden.",
|
||||
"RemotePathMappingCheckGenericPermissions": "Downloader {0} speichert Downloads in {1}, aber Radarr kann dieses Verzeichnis nicht sehen. Möchlicherweise müssen die Verzeichnisrechte angepasst werden.",
|
||||
"RemotePathMappingCheckWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Remote-Pfadzuordnungen und die Downloader Einstellungen.",
|
||||
"RemotePathMappingCheckLocalWrongOSPath": "Downloader {0} speichert Downloads in {1}, aber dies ist kein valider {2} Pfad. Überprüfe die Downloader Einstellungen.",
|
||||
"RemotePathMappingCheckLocalFolderMissing": "Downloader {0} speichert Downloads in {1}, aber dieses Verzeichnis scheint nicht zu existieren. Möglicherweise eine fehlende oder falsche Remote-Pfadzuordnung.",
|
||||
@@ -1112,10 +1112,10 @@
|
||||
"OriginalTitle": "Originaler Titel",
|
||||
"OriginalLanguage": "Originale Sprache",
|
||||
"Database": "Datenbank",
|
||||
"AllCollectionsHiddenDueToFilter": "Alle Filme sind auf Grund des Filters ausgeblendet.",
|
||||
"Collections": "Sammlungen",
|
||||
"AllCollectionsHiddenDueToFilter": "Alle Filme sind wegen dem Filter ausgeblendet.",
|
||||
"Collections": "Sammlung",
|
||||
"MonitorMovies": "Film beobachten",
|
||||
"NoCollections": "Keine Filme gefunden. Zum Starten solltest du einen Film hinzufügen oder vorhandene Importieren",
|
||||
"NoCollections": "Keine Filme gefunden. Zum Starten solltest du einen Film hinzufügen oder vorhandene Importieren.",
|
||||
"RssSyncHelpText": "Intervall in Minuten. Zum deaktivieren auf 0 setzen ( Dies wird das automatische Release erfassen deaktivieren )",
|
||||
"CollectionOptions": "Sammlung Optionen",
|
||||
"ChooseImportMode": "Wähle eine Importmethode",
|
||||
@@ -1127,7 +1127,7 @@
|
||||
"ScrollMovies": "Filme scrollen",
|
||||
"ShowCollectionDetails": "Status der Sammlung anzeigen",
|
||||
"RefreshCollections": "Sammlungen aktualisieren",
|
||||
"RefreshMonitoredIntervalHelpText": "Wie häufig die beobachteten Downloads von Download-Clients aktualisiert werden sollen (min. 1 Minute)",
|
||||
"RefreshMonitoredIntervalHelpText": "Wie häufig die beobachteten Downloads von Download-Clients aktualisiert werden sollen (Min. 1 Minute).",
|
||||
"ShowOverview": "Übersicht anzeigen",
|
||||
"ShowPosters": "Plakate anzeigen",
|
||||
"CollectionShowDetailsHelpText": "Status und Eigenschaften der Sammlung anzeigen",
|
||||
@@ -1141,6 +1141,5 @@
|
||||
"OnMovieAddedHelpText": "Ausführen wenn ein Film hinzugefügt wurde",
|
||||
"UnableToLoadCollections": "Sammlungen können nicht geladen werden",
|
||||
"InstanceName": "Instanzname",
|
||||
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname",
|
||||
"RottenTomatoesRating": "Tomato Bewertung"
|
||||
"InstanceNameHelpText": "Instanzname im Browser-Tab und für Syslog-Anwendungsname"
|
||||
}
|
||||
|
||||
@@ -1027,7 +1027,6 @@
|
||||
"Torrents": "Torrents",
|
||||
"TorrentsDisabled": "Torrents Disabled",
|
||||
"TotalFileSize": "Total File Size",
|
||||
"TotalMovies": "Total Movies",
|
||||
"TotalSpace": "Total Space",
|
||||
"Trace": "Trace",
|
||||
"Trailer": "Trailer",
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
"MoveFiles": "Siirrä tiedostoja",
|
||||
"MovieFiles": "Elokuvatiedostot",
|
||||
"MovieIsRecommend": "Elokuvaa suositellaan viimeaikaisen lisäyksen perusteella",
|
||||
"NextExecution": "Seuraava suoritus",
|
||||
"NextExecution": "Seuraava toteutus",
|
||||
"NoAltTitle": "Ei vaihtoehtoisia nimikkeitä.",
|
||||
"NoEventsFound": "Tapahtumia ei löytynyt",
|
||||
"NoLinks": "Ei linkkejä",
|
||||
@@ -320,8 +320,8 @@
|
||||
"ImportErrors": "Tuontivirheet",
|
||||
"Imported": "Tuodut",
|
||||
"InvalidFormat": "Väärä muoto",
|
||||
"LastDuration": "Edellinen kesto",
|
||||
"LastExecution": "Edellinen suoritus",
|
||||
"LastDuration": "Viimeisin kesto",
|
||||
"LastExecution": "Viimeinen toteutus",
|
||||
"ListSyncLevelHelpTextWarning": "Elokuvatiedostot poistetaan pysyvästi, mikä voi johtaa kirjaston pyyhkimiseen, jos luettelosi ovat tyhjät",
|
||||
"ListExclusions": "Listojen poikkeussäännöt",
|
||||
"Indexer": "Tietolähde",
|
||||
|
||||
@@ -318,7 +318,7 @@
|
||||
"ChangeHasNotBeenSavedYet": "Les changements n'ont pas encore été sauvegardés",
|
||||
"ChangeFileDate": "Changer la date du fichier",
|
||||
"CertificationCountryHelpText": "Choisir un pays pour les classifications de films",
|
||||
"CertificateValidationHelpText": "Modifier le degré de rigueur de la validation de la certification HTTPS. Ne pas changer à moins que vous ne compreniez les risques.",
|
||||
"CertificateValidationHelpText": "Change la rigueur de la vérification du certificat HTTPS. Ne changez rien sauf si vous comprenez les risques.",
|
||||
"CertificateValidation": "Validation du certificat",
|
||||
"BypassProxyForLocalAddresses": "Contourner le proxy pour les adresses locales",
|
||||
"Branch": "Branche",
|
||||
@@ -1119,8 +1119,5 @@
|
||||
"NoCollections": "Aucun film trouvé, pour commencer, vous voudrez ajouter un nouveau film ou importer des films existants.",
|
||||
"RssSyncHelpText": "Intervalle en minutes. Mettre à zéro pour désactiver (cela arrêtera tous les téléchargements automatiques)",
|
||||
"CollectionsSelectedInterp": "{0} Collections(s) Sélectionnée(s)",
|
||||
"ChooseImportMode": "Mode d'importation",
|
||||
"CollectionOptions": "Options de collection",
|
||||
"CollectionShowDetailsHelpText": "Afficher l'état et les propriétés de la collection",
|
||||
"CollectionShowOverviewsHelpText": "Afficher les aperçus des collections"
|
||||
"ChooseImportMode": "Mode d'importation"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"LastExecution": "Última Execução",
|
||||
"LastExecution": "Execução mais recente",
|
||||
"Large": "Grande",
|
||||
"Languages": "Idiomas",
|
||||
"LanguageHelpText": "Idioma das versões",
|
||||
@@ -19,7 +19,7 @@
|
||||
"IndexerStatusCheckAllClientMessage": "Todos os indexadores estão indisponíveis devido a falhas",
|
||||
"IndexersSettingsSummary": "Indexadores e restrições de lançamento",
|
||||
"IndexerSettings": "Configurações do indexador",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa interativa ativada, o Radarr não fornecerá nenhum resultado de pesquisa interativo",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa Interativa ativada, o Radarr não fornecerá nenhum resultado de pesquisa interativa",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Todos os indexadores com capacidade de pesquisa estão temporariamente indisponíveis devido a erros recentes do indexador",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "Nenhum indexador disponível com a Pesquisa automática habilitada, o Radarr não fornecerá nenhum resultado de pesquisa automática",
|
||||
"Indexers": "Indexadores",
|
||||
@@ -571,7 +571,7 @@
|
||||
"NoChange": "Sem alteração",
|
||||
"NoBackupsAreAvailable": "Não há backups disponíveis",
|
||||
"NoAltTitle": "Nenhum título alternativo.",
|
||||
"NextExecution": "Próxima Execução",
|
||||
"NextExecution": "Próxima execução",
|
||||
"New": "Novo",
|
||||
"NetCore": ".NET",
|
||||
"ShowAsAllDayEvents": "Mostrar como eventos de dia inteiro",
|
||||
@@ -668,7 +668,7 @@
|
||||
"SomeResultsHiddenFilter": "Alguns resultados estão ocultos pelo filtro aplicado",
|
||||
"Socks5": "Socks5 (suporte ao TOR)",
|
||||
"Small": "Pequeno",
|
||||
"SkipFreeSpaceCheckWhenImportingHelpText": "Use quando o Radarr não conseguir detectar espaço livre na pasta raiz do filme",
|
||||
"SkipFreeSpaceCheckWhenImportingHelpText": "Usar o Radarr quando for impossível detectar o espaço livre da sua pasta raiz do filme",
|
||||
"SkipFreeSpaceCheck": "Ignorar verificação de espaço livre",
|
||||
"SizeOnDisk": "Tamanho em disco",
|
||||
"Size": "Tamanho",
|
||||
@@ -961,7 +961,7 @@
|
||||
"UpdateSelected": "Atualizar selecionado(s)",
|
||||
"UpdateScriptPathHelpText": "Caminho para um script personalizado que usa um pacote de atualização extraído e lida com o restante do processo de atualização",
|
||||
"Updates": "Atualizações",
|
||||
"UpdateMechanismHelpText": "Use o atualizador integrado do Radarr ou um script",
|
||||
"UpdateMechanismHelpText": "Usar atualizador integrado do Radarr ou um script",
|
||||
"UpdateCheckUINotWritableMessage": "Não é possível instalar a atualização porque a pasta de interface do usuário '{0}' não é gravável pelo usuário '{1}'.",
|
||||
"UpdateCheckStartupTranslocationMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' está em uma pasta App Translocation.",
|
||||
"UpdateCheckStartupNotWritableMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' não pode ser gravada pelo usuário '{1}'.",
|
||||
@@ -1053,7 +1053,7 @@
|
||||
"RemotePathMappingCheckImportFailed": "O Radarr não conseguiu importar um filme. Verifique os logs para saber mais.",
|
||||
"RemotePathMappingCheckFileRemoved": "O arquivo {0} foi removido no meio do processamento.",
|
||||
"RemotePathMappingCheckDownloadPermissions": "O Radarr pode ver, mas não pode acessar o filme baixado {0}. Provável erro de permissões.",
|
||||
"RemotePathMappingCheckGenericPermissions": "O cliente de download {0} coloca downloads em {1} mas o Radarr não pode ver este diretório. Pode ser necessário ajustar as permissões da pasta.",
|
||||
"RemotePathMappingCheckGenericPermissions": "O cliente para download {0} põe os downloads em {1}, mas o Radarr não pode ver esse diretório. Você pode precisar ajustar as permissões da pasta.",
|
||||
"RemotePathMappingCheckWrongOSPath": "O cliente de download remoto {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise seus mapeamentos de caminho remoto e baixe as configurações do cliente.",
|
||||
"RemotePathMappingCheckLocalWrongOSPath": "O cliente de download local {0} coloca downloads em {1}, mas este não é um caminho {2} válido. Revise as configurações do seu cliente de download.",
|
||||
"RemotePathMappingCheckLocalFolderMissing": "O cliente de download remoto {0} coloca downloads em {1}, mas esse diretório parece não existir. Mapeamento de caminho remoto provavelmente ausente ou incorreto.",
|
||||
|
||||
@@ -327,13 +327,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
}
|
||||
else if (movie.ImdbId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
newMovie = _movieMetadataService.FindByImdbId(Parser.Parser.NormalizeImdbId(movie.ImdbId));
|
||||
|
||||
if (newMovie != null)
|
||||
{
|
||||
return newMovie;
|
||||
}
|
||||
|
||||
newMovie = GetMovieByImdbId(movie.ImdbId);
|
||||
}
|
||||
else
|
||||
@@ -344,7 +337,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
yearStr = $" {movie.Year}";
|
||||
}
|
||||
|
||||
newMovie = SearchForNewMovie(movie.Title + yearStr).FirstOrDefault()?.MovieMetadata ?? null;
|
||||
newMovie = SearchForNewMovie(movie.Title + yearStr).FirstOrDefault().MovieMetadata;
|
||||
}
|
||||
|
||||
if (newMovie == null)
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace NzbDrone.Core.Movies.Collections
|
||||
|
||||
var collection = FindByTmdbId(collectionTmdbId);
|
||||
|
||||
_repo.Delete(collection.Id);
|
||||
_repo.Delete(collectionTmdbId);
|
||||
|
||||
_eventAggregator.PublishEvent(new CollectionDeletedEvent(collection));
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace NzbDrone.Core.Movies
|
||||
public interface IMovieMetadataRepository : IBasicRepository<MovieMetadata>
|
||||
{
|
||||
MovieMetadata FindByTmdbId(int tmdbId);
|
||||
MovieMetadata FindByImdbId(string imdbId);
|
||||
List<MovieMetadata> FindById(List<int> tmdbIds);
|
||||
List<MovieMetadata> GetMoviesWithCollections();
|
||||
List<MovieMetadata> GetMoviesByCollectionTmdbId(int collectionId);
|
||||
@@ -28,14 +27,9 @@ namespace NzbDrone.Core.Movies
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public MovieMetadata FindByTmdbId(int tmdbId)
|
||||
public MovieMetadata FindByTmdbId(int tmdbid)
|
||||
{
|
||||
return Query(x => x.TmdbId == tmdbId).FirstOrDefault();
|
||||
}
|
||||
|
||||
public MovieMetadata FindByImdbId(string imdbId)
|
||||
{
|
||||
return Query(x => x.ImdbId == imdbId).FirstOrDefault();
|
||||
return Query(x => x.TmdbId == tmdbid).FirstOrDefault();
|
||||
}
|
||||
|
||||
public List<MovieMetadata> FindById(List<int> tmdbIds)
|
||||
|
||||
@@ -5,8 +5,7 @@ namespace NzbDrone.Core.Movies
|
||||
public interface IMovieMetadataService
|
||||
{
|
||||
MovieMetadata Get(int id);
|
||||
MovieMetadata FindByTmdbId(int tmdbId);
|
||||
MovieMetadata FindByImdbId(string imdbId);
|
||||
MovieMetadata FindByTmdbId(int tmdbid);
|
||||
List<MovieMetadata> GetMoviesWithCollections();
|
||||
List<MovieMetadata> GetMoviesByCollectionTmdbId(int collectionId);
|
||||
bool Upsert(MovieMetadata movie);
|
||||
@@ -22,14 +21,9 @@ namespace NzbDrone.Core.Movies
|
||||
_movieMetadataRepository = movieMetadataRepository;
|
||||
}
|
||||
|
||||
public MovieMetadata FindByTmdbId(int tmdbId)
|
||||
public MovieMetadata FindByTmdbId(int tmdbid)
|
||||
{
|
||||
return _movieMetadataRepository.FindByTmdbId(tmdbId);
|
||||
}
|
||||
|
||||
public MovieMetadata FindByImdbId(string imdbId)
|
||||
{
|
||||
return _movieMetadataRepository.FindByImdbId(imdbId);
|
||||
return _movieMetadataRepository.FindByTmdbId(tmdbid);
|
||||
}
|
||||
|
||||
public List<MovieMetadata> GetMoviesWithCollections()
|
||||
|
||||
@@ -3,11 +3,12 @@ using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.ImportLists.ImportExclusions;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.Movies.Collections;
|
||||
using NzbDrone.Core.Movies.Commands;
|
||||
using NzbDrone.Core.Movies.Events;
|
||||
|
||||
namespace NzbDrone.Core.Movies
|
||||
{
|
||||
@@ -18,7 +19,6 @@ namespace NzbDrone.Core.Movies
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly IMovieMetadataService _movieMetadataService;
|
||||
private readonly IAddMovieService _addMovieService;
|
||||
private readonly IImportExclusionsService _importExclusionService;
|
||||
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -27,7 +27,6 @@ namespace NzbDrone.Core.Movies
|
||||
IMovieService movieService,
|
||||
IMovieMetadataService movieMetadataService,
|
||||
IAddMovieService addMovieService,
|
||||
IImportExclusionsService importExclusionsService,
|
||||
Logger logger)
|
||||
{
|
||||
_movieInfo = movieInfo;
|
||||
@@ -35,7 +34,6 @@ namespace NzbDrone.Core.Movies
|
||||
_movieService = movieService;
|
||||
_movieMetadataService = movieMetadataService;
|
||||
_addMovieService = addMovieService;
|
||||
_importExclusionService = importExclusionsService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -101,8 +99,7 @@ namespace NzbDrone.Core.Movies
|
||||
{
|
||||
var existingMovies = _movieService.AllMovieTmdbIds();
|
||||
var collectionMovies = _movieMetadataService.GetMoviesByCollectionTmdbId(collection.TmdbId);
|
||||
var excludedMovies = _importExclusionService.GetAllExclusions().Select(e => e.TmdbId);
|
||||
var moviesToAdd = collectionMovies.Where(m => !existingMovies.Contains(m.TmdbId)).Where(m => !excludedMovies.Contains(m.TmdbId));
|
||||
var moviesToAdd = collectionMovies.Where(m => !existingMovies.Contains(m.TmdbId));
|
||||
|
||||
if (moviesToAdd.Any())
|
||||
{
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace NzbDrone.Core.Movies
|
||||
SearchOnAdd = movie.AddOptions?.SearchForMovie ?? false,
|
||||
QualityProfileId = movie.ProfileId,
|
||||
MinimumAvailability = movie.MinimumAvailability,
|
||||
RootFolderPath = _folderService.GetBestRootFolderPath(movie.Path).TrimEnd('/', '\\', ' ')
|
||||
RootFolderPath = _folderService.GetBestRootFolderPath(movie.Path)
|
||||
});
|
||||
|
||||
movieMetadata.CollectionTmdbId = newCollection.TmdbId;
|
||||
|
||||
@@ -48,8 +48,6 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
variables.Add("Radarr_Download_Client", message.DownloadClientName ?? string.Empty);
|
||||
variables.Add("Radarr_Download_Client_Type", message.DownloadClientType ?? string.Empty);
|
||||
variables.Add("Radarr_Download_Id", message.DownloadId ?? string.Empty);
|
||||
variables.Add("Radarr_Release_CustomFormat", string.Join("|", remoteMovie.CustomFormats));
|
||||
variables.Add("Radarr_Release_CustomFormatScore", remoteMovie.CustomFormatScore.ToString());
|
||||
|
||||
_proxy.SendNotification(variables, Settings);
|
||||
}
|
||||
|
||||
@@ -54,19 +54,17 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
_logger.Error(ex, "API key is invalid: " + ex.Message);
|
||||
return new ValidationFailure("APIKey", "API key is invalid");
|
||||
case 400:
|
||||
_logger.Error(ex, "Unable to send test message. Ensure Radarr Integration is enabled & assigned a channel on Notifiarr");
|
||||
return new ValidationFailure("", "Unable to send test message. Ensure Radarr Integration is enabled & assigned a channel on Notifiarr");
|
||||
case 520:
|
||||
case 521:
|
||||
case 522:
|
||||
case 523:
|
||||
case 524:
|
||||
_logger.Error(ex, "Cloudflare Related HTTP Error - Unable to send test message: " + ex.Message);
|
||||
return new ValidationFailure("", "Cloudflare Related HTTP Error - Unable to send test message");
|
||||
_logger.Error(ex, "Unable to send test notification: " + ex.Message);
|
||||
return new ValidationFailure("", "Unable to send test notification");
|
||||
}
|
||||
|
||||
_logger.Error(ex, "Unknown HTTP Error - Unable to send test message: " + ex.Message);
|
||||
return new ValidationFailure("", "Unknown HTTP Error - Unable to send test message");
|
||||
_logger.Error(ex, "Unable to send test message: " + ex.Message);
|
||||
return new ValidationFailure("APIKey", "Unable to send test notification");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -97,21 +95,19 @@ namespace NzbDrone.Core.Notifications.Notifiarr
|
||||
switch ((int)ex.Response.StatusCode)
|
||||
{
|
||||
case 401:
|
||||
_logger.Error("", "API key is invalid");
|
||||
throw new NotifiarrException("API key is invalid", ex);
|
||||
_logger.Error(ex, "API key is invalid");
|
||||
throw;
|
||||
case 400:
|
||||
_logger.Error(ex, "Unable to send notification. Ensure Radarr Integration is enabled & assigned a channel on Notifiarr");
|
||||
throw new NotifiarrException("Unable to send notification. Ensure Radarr Integration is enabled & assigned a channel on Notifiarr", ex);
|
||||
case 520:
|
||||
case 521:
|
||||
case 522:
|
||||
case 523:
|
||||
case 524:
|
||||
_logger.Error(ex, "Cloudflare Related HTTP Error - Unable to send notification");
|
||||
throw new NotifiarrException("Cloudflare Related HTTP Error - Unable to send notification", ex);
|
||||
_logger.Error(ex, "Unable to send notification");
|
||||
throw;
|
||||
}
|
||||
|
||||
throw new NotifiarrException("Unknown HTTP Error - Unable to send notification", ex);
|
||||
throw new NotifiarrException("Unable to send notification", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Parser
|
||||
(?<scr>SCR|SCREENER|DVDSCR|DVDSCREENER)|
|
||||
(?<ts>TS[-_. ]|TELESYNC|HD-TS|HDTS|PDVD|TSRip|HDTSRip)|
|
||||
(?<tc>TC|TELECINE|HD-TC|HDTC)|
|
||||
(?<cam>CAMRIP|CAM|HDCAM|HQCAM|HD-CAM)|
|
||||
(?<cam>CAMRIP|CAM|HDCAM|HD-CAM)|
|
||||
(?<wp>WORKPRINT|WP)|
|
||||
(?<pdtv>PDTV)|
|
||||
(?<sdtv>SDTV)|
|
||||
|
||||
@@ -55,7 +55,9 @@ namespace Radarr.Api.V3.Collections
|
||||
[HttpGet]
|
||||
public List<CollectionResource> GetCollections()
|
||||
{
|
||||
return MapToResource(_collectionService.GetAllCollections()).ToList();
|
||||
var collectionMovies = _movieMetadataService.GetMoviesWithCollections();
|
||||
|
||||
return MapToResource(_collectionService.GetAllCollections(), collectionMovies).ToList();
|
||||
}
|
||||
|
||||
[RestPutById]
|
||||
@@ -114,11 +116,10 @@ namespace Radarr.Api.V3.Collections
|
||||
return Accepted(updated);
|
||||
}
|
||||
|
||||
private IEnumerable<CollectionResource> MapToResource(List<MovieCollection> collections)
|
||||
private IEnumerable<CollectionResource> MapToResource(List<MovieCollection> collections, List<MovieMetadata> collectionMovies)
|
||||
{
|
||||
// Avoid calling for naming spec on every movie in filenamebuilder
|
||||
var namingConfig = _namingService.GetConfig();
|
||||
var collectionMovies = _movieMetadataService.GetMoviesWithCollections();
|
||||
|
||||
foreach (var collection in collections)
|
||||
{
|
||||
@@ -166,7 +167,7 @@ namespace Radarr.Api.V3.Collections
|
||||
[NonAction]
|
||||
public void Handle(CollectionDeletedEvent message)
|
||||
{
|
||||
BroadcastResourceChange(ModelAction.Deleted, message.Collection.Id);
|
||||
BroadcastResourceChange(ModelAction.Deleted, MapToResource(message.Collection));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.CustomFormats;
|
||||
using NzbDrone.Core.Validation;
|
||||
using Radarr.Http;
|
||||
using Radarr.Http.REST;
|
||||
using Radarr.Http.REST.Attributes;
|
||||
@@ -52,9 +48,6 @@ namespace Radarr.Api.V3.CustomFormats
|
||||
public ActionResult<CustomFormatResource> Create(CustomFormatResource customFormatResource)
|
||||
{
|
||||
var model = customFormatResource.ToModel(_specifications);
|
||||
|
||||
Validate(model);
|
||||
|
||||
return Created(_formatService.Insert(model).Id);
|
||||
}
|
||||
|
||||
@@ -62,9 +55,6 @@ namespace Radarr.Api.V3.CustomFormats
|
||||
public ActionResult<CustomFormatResource> Update(CustomFormatResource resource)
|
||||
{
|
||||
var model = resource.ToModel(_specifications);
|
||||
|
||||
Validate(model);
|
||||
|
||||
_formatService.Update(model);
|
||||
|
||||
return Accepted(model.Id);
|
||||
@@ -97,25 +87,6 @@ namespace Radarr.Api.V3.CustomFormats
|
||||
return schema;
|
||||
}
|
||||
|
||||
private void Validate(CustomFormat definition)
|
||||
{
|
||||
foreach (var spec in definition.Specifications)
|
||||
{
|
||||
var validationResult = spec.Validate();
|
||||
VerifyValidationResult(validationResult);
|
||||
}
|
||||
}
|
||||
|
||||
protected void VerifyValidationResult(ValidationResult validationResult)
|
||||
{
|
||||
var result = new NzbDroneValidationResult(validationResult.Errors);
|
||||
|
||||
if (!result.IsValid)
|
||||
{
|
||||
throw new ValidationException(result.Errors);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<ICustomFormatSpecification> GetPresets()
|
||||
{
|
||||
yield return new ReleaseTitleSpecification
|
||||
|
||||
@@ -11,7 +11,6 @@ using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Organizer;
|
||||
using Radarr.Api.V3.Movies;
|
||||
using Radarr.Http;
|
||||
|
||||
namespace Radarr.Api.V3.ImportLists
|
||||
@@ -20,7 +19,6 @@ namespace Radarr.Api.V3.ImportLists
|
||||
public class ImportListMoviesController : Controller
|
||||
{
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly IAddMovieService _addMovieService;
|
||||
private readonly IProvideMovieInfo _movieInfo;
|
||||
private readonly IBuildFileNames _fileNameBuilder;
|
||||
private readonly IImportListMovieService _listMovieService;
|
||||
@@ -30,7 +28,6 @@ namespace Radarr.Api.V3.ImportLists
|
||||
private readonly IConfigService _configService;
|
||||
|
||||
public ImportListMoviesController(IMovieService movieService,
|
||||
IAddMovieService addMovieService,
|
||||
IProvideMovieInfo movieInfo,
|
||||
IBuildFileNames fileNameBuilder,
|
||||
IImportListMovieService listMovieService,
|
||||
@@ -40,7 +37,6 @@ namespace Radarr.Api.V3.ImportLists
|
||||
IConfigService configService)
|
||||
{
|
||||
_movieService = movieService;
|
||||
_addMovieService = addMovieService;
|
||||
_movieInfo = movieInfo;
|
||||
_fileNameBuilder = fileNameBuilder;
|
||||
_listMovieService = listMovieService;
|
||||
@@ -96,14 +92,6 @@ namespace Radarr.Api.V3.ImportLists
|
||||
return realResults;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public object AddMovies([FromBody] List<MovieResource> resource)
|
||||
{
|
||||
var newMovies = resource.ToModel();
|
||||
|
||||
return _addMovieService.AddMovies(newMovies, true).ToResource(0);
|
||||
}
|
||||
|
||||
private IEnumerable<ImportListMoviesResource> MapToResource(IEnumerable<Movie> movies, Language language)
|
||||
{
|
||||
//Avoid calling for naming spec on every movie in filenamebuilder
|
||||
|
||||
@@ -3315,44 +3315,6 @@
|
||||
"description": "Success"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"ImportListMovies"
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MovieResource"
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MovieResource"
|
||||
}
|
||||
}
|
||||
},
|
||||
"application/*+json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MovieResource"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/indexer": {
|
||||
|
||||
Reference in New Issue
Block a user