mirror of
https://github.com/Radarr/Radarr.git
synced 2025-04-24 06:27:08 -04:00
New: Add support for additional Torznab indexer flags
This commit is contained in:
parent
a2bde5e016
commit
ff3d38a515
12 changed files with 123 additions and 93 deletions
|
@ -8,6 +8,7 @@ import Language from 'Language/Language';
|
|||
import DownloadClient from 'typings/DownloadClient';
|
||||
import ImportList from 'typings/ImportList';
|
||||
import Indexer from 'typings/Indexer';
|
||||
import IndexerFlag from 'typings/IndexerFlag';
|
||||
import Notification from 'typings/Notification';
|
||||
import QualityProfile from 'typings/QualityProfile';
|
||||
import { UiSettings } from 'typings/UiSettings';
|
||||
|
@ -35,12 +36,14 @@ export interface QualityProfilesAppState
|
|||
extends AppSectionState<QualityProfile>,
|
||||
AppSectionSchemaState<QualityProfile> {}
|
||||
|
||||
export type IndexerFlagSettingsAppState = AppSectionState<IndexerFlag>;
|
||||
export type LanguageSettingsAppState = AppSectionState<Language>;
|
||||
export type UiSettingsAppState = AppSectionItemState<UiSettings>;
|
||||
|
||||
interface SettingsAppState {
|
||||
downloadClients: DownloadClientAppState;
|
||||
importLists: ImportListAppState;
|
||||
indexerFlags: IndexerFlagSettingsAppState;
|
||||
indexers: IndexerAppState;
|
||||
languages: LanguageSettingsAppState;
|
||||
notifications: NotificationAppState;
|
||||
|
|
|
@ -12,7 +12,7 @@ import DownloadClientSelectInputConnector from './DownloadClientSelectInputConne
|
|||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
|
||||
import FormInputHelpText from './FormInputHelpText';
|
||||
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
|
||||
import IndexerFlagsSelectInput from './IndexerFlagsSelectInput';
|
||||
import IndexerSelectInputConnector from './IndexerSelectInputConnector';
|
||||
import KeyValueListInput from './KeyValueListInput';
|
||||
import LanguageSelectInputConnector from './LanguageSelectInputConnector';
|
||||
|
@ -76,7 +76,7 @@ function getComponent(type) {
|
|||
return RootFolderSelectInputConnector;
|
||||
|
||||
case inputTypes.INDEXER_FLAGS_SELECT:
|
||||
return IndexerFlagsSelectInputConnector;
|
||||
return IndexerFlagsSelectInput;
|
||||
|
||||
case inputTypes.DOWNLOAD_CLIENT_SELECT:
|
||||
return DownloadClientSelectInputConnector;
|
||||
|
|
60
frontend/src/Components/Form/IndexerFlagsSelectInput.tsx
Normal file
60
frontend/src/Components/Form/IndexerFlagsSelectInput.tsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
|
||||
interface IndexerFlagsSelectInputProps {
|
||||
name: string;
|
||||
indexerFlags: number;
|
||||
onChange(payload: object): void;
|
||||
}
|
||||
|
||||
const selectIndexerFlagsValues = (selectedFlags: number) =>
|
||||
createSelector(
|
||||
(state: AppState) => state.settings.indexerFlags,
|
||||
(indexerFlags) => {
|
||||
const value = indexerFlags.items
|
||||
.filter(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
(item) => (selectedFlags & item.id) === item.id
|
||||
)
|
||||
.map(({ id }) => id);
|
||||
|
||||
const values = indexerFlags.items.map(({ id, name }) => ({
|
||||
key: id,
|
||||
value: name,
|
||||
}));
|
||||
|
||||
return {
|
||||
value,
|
||||
values,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
function IndexerFlagsSelectInput(props: IndexerFlagsSelectInputProps) {
|
||||
const { indexerFlags, onChange } = props;
|
||||
|
||||
const { value, values } = useSelector(selectIndexerFlagsValues(indexerFlags));
|
||||
|
||||
const onChangeWrapper = useCallback(
|
||||
({ name, value }: { name: string; value: number[] }) => {
|
||||
const indexerFlags = value.reduce((acc, flagId) => acc + flagId, 0);
|
||||
|
||||
onChange({ name, value: indexerFlags });
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<EnhancedSelectInput
|
||||
{...props}
|
||||
value={value}
|
||||
values={values}
|
||||
onChange={onChangeWrapper}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default IndexerFlagsSelectInput;
|
|
@ -1,70 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { indexerFlags }) => indexerFlags,
|
||||
(state) => state.settings.indexerFlags,
|
||||
(selectedFlags, indexerFlags) => {
|
||||
const value = [];
|
||||
|
||||
indexerFlags.items.forEach((item) => {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
if ((selectedFlags & item.id) === item.id) {
|
||||
value.push(item.id);
|
||||
}
|
||||
});
|
||||
|
||||
const values = indexerFlags.items.map(({ id, name }) => {
|
||||
return {
|
||||
key: id,
|
||||
value: name
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
value,
|
||||
values
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class IndexerFlagsSelectInputConnector extends Component {
|
||||
|
||||
onChange = ({ name, value }) => {
|
||||
let indexerFlags = 0;
|
||||
|
||||
value.forEach((flagId) => {
|
||||
indexerFlags += flagId;
|
||||
});
|
||||
|
||||
this.props.onChange({ name, value: indexerFlags });
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<EnhancedSelectInput
|
||||
{...this.props}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
IndexerFlagsSelectInputConnector.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
indexerFlags: PropTypes.number.isRequired,
|
||||
value: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps)(IndexerFlagsSelectInputConnector);
|
|
@ -0,0 +1,9 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
|
||||
const createIndexerFlagsSelector = createSelector(
|
||||
(state: AppState) => state.settings.indexerFlags,
|
||||
(indexerFlags) => indexerFlags
|
||||
);
|
||||
|
||||
export default createIndexerFlagsSelector;
|
6
frontend/src/typings/IndexerFlag.ts
Normal file
6
frontend/src/typings/IndexerFlag.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
interface IndexerFlag {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default IndexerFlag;
|
|
@ -85,17 +85,12 @@ namespace NzbDrone.Core.DecisionEngine
|
|||
|
||||
private int CompareIndexerFlags(DownloadDecision x, DownloadDecision y)
|
||||
{
|
||||
var releaseX = x.RemoteMovie.Release;
|
||||
var releaseY = y.RemoteMovie.Release;
|
||||
|
||||
if (_configService.PreferIndexerFlags)
|
||||
{
|
||||
return CompareBy(x.RemoteMovie.Release, y.RemoteMovie.Release, release => ScoreFlags(release.IndexerFlags));
|
||||
}
|
||||
else
|
||||
if (!_configService.PreferIndexerFlags)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return CompareBy(x.RemoteMovie.Release, y.RemoteMovie.Release, release => ScoreFlags(release.IndexerFlags));
|
||||
}
|
||||
|
||||
private int CompareProtocol(DownloadDecision x, DownloadDecision y)
|
||||
|
@ -206,12 +201,10 @@ namespace NzbDrone.Core.DecisionEngine
|
|||
case IndexerFlags.G_Freeleech:
|
||||
case IndexerFlags.PTP_Approved:
|
||||
case IndexerFlags.PTP_Golden:
|
||||
case IndexerFlags.HDB_Internal:
|
||||
case IndexerFlags.AHD_Internal:
|
||||
case IndexerFlags.G_Internal:
|
||||
score += 2;
|
||||
break;
|
||||
case IndexerFlags.G_Halfleech:
|
||||
case IndexerFlags.AHD_UserRelease:
|
||||
score += 1;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
|||
public uint Files { get; set; }
|
||||
[JsonProperty(PropertyName = "imdb")]
|
||||
public string ImdbId { get; set; }
|
||||
public bool Internal { get; set; }
|
||||
[JsonProperty(PropertyName = "freeleech")]
|
||||
public bool FreeLeech { get; set; }
|
||||
[JsonProperty(PropertyName = "upload_date")]
|
||||
|
|
|
@ -41,15 +41,20 @@ namespace NzbDrone.Core.Indexers.FileList
|
|||
flags |= IndexerFlags.G_Freeleech;
|
||||
}
|
||||
|
||||
if (result.Internal)
|
||||
{
|
||||
flags |= IndexerFlags.G_Internal;
|
||||
}
|
||||
|
||||
var imdbId = 0;
|
||||
if (result.ImdbId != null && result.ImdbId.Length > 2)
|
||||
{
|
||||
imdbId = int.Parse(result.ImdbId.Substring(2));
|
||||
}
|
||||
|
||||
torrentInfos.Add(new TorrentInfo()
|
||||
torrentInfos.Add(new TorrentInfo
|
||||
{
|
||||
Guid = string.Format("FileList-{0}", id),
|
||||
Guid = $"FileList-{id}",
|
||||
Title = result.Name,
|
||||
Size = result.Size,
|
||||
DownloadUrl = GetDownloadUrl(id),
|
||||
|
|
|
@ -62,7 +62,7 @@ namespace NzbDrone.Core.Indexers.HDBits
|
|||
|
||||
if (internalRelease)
|
||||
{
|
||||
flags |= IndexerFlags.HDB_Internal;
|
||||
flags |= IndexerFlags.G_Internal;
|
||||
}
|
||||
|
||||
torrentInfos.Add(new HDBitsInfo()
|
||||
|
|
|
@ -208,24 +208,45 @@ namespace NzbDrone.Core.Indexers.Torznab
|
|||
IndexerFlags flags = 0;
|
||||
|
||||
var downloadFactor = TryGetFloatTorznabAttribute(item, "downloadvolumefactor", 1);
|
||||
|
||||
var uploadFactor = TryGetFloatTorznabAttribute(item, "uploadvolumefactor", 1);
|
||||
|
||||
if (uploadFactor == 2)
|
||||
{
|
||||
flags |= IndexerFlags.G_DoubleUpload;
|
||||
}
|
||||
|
||||
if (downloadFactor == 0.5)
|
||||
{
|
||||
flags |= IndexerFlags.G_Halfleech;
|
||||
}
|
||||
|
||||
if (downloadFactor == 0.75)
|
||||
{
|
||||
flags |= IndexerFlags.G_Freeleech25;
|
||||
}
|
||||
|
||||
if (downloadFactor == 0.25)
|
||||
{
|
||||
flags |= IndexerFlags.G_Freeleech75;
|
||||
}
|
||||
|
||||
if (downloadFactor == 0.0)
|
||||
{
|
||||
flags |= IndexerFlags.G_Freeleech;
|
||||
}
|
||||
|
||||
if (uploadFactor == 2.0)
|
||||
{
|
||||
flags |= IndexerFlags.G_DoubleUpload;
|
||||
}
|
||||
|
||||
var tags = TryGetMultipleTorznabAttributes(item, "tag");
|
||||
|
||||
if (tags.Any(t => t.EqualsIgnoreCase("internal")))
|
||||
{
|
||||
flags |= IndexerFlags.G_Internal;
|
||||
}
|
||||
|
||||
if (tags.Any(t => t.EqualsIgnoreCase("scene")))
|
||||
{
|
||||
flags |= IndexerFlags.G_Scene;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
|
|
@ -106,11 +106,13 @@ namespace NzbDrone.Core.Parser.Model
|
|||
G_DoubleUpload = 4, // General
|
||||
PTP_Golden = 8, // PTP
|
||||
PTP_Approved = 16, // PTP
|
||||
HDB_Internal = 32, // HDBits, internal
|
||||
G_Internal = 32, // General, internal
|
||||
[Obsolete]
|
||||
AHD_Internal = 64, // AHD, internal
|
||||
G_Scene = 128, // General, the torrent comes from the "scene"
|
||||
G_Freeleech75 = 256, // Currently only used for AHD, signifies a torrent counts towards 75 percent of your download quota.
|
||||
G_Freeleech25 = 512, // Currently only used for AHD, signifies a torrent counts towards 25 percent of your download quota.
|
||||
[Obsolete]
|
||||
AHD_UserRelease = 1024 // AHD, internal
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue