New: Add support for additional Torznab indexer flags

pull/9148/head
Bogdan 9 months ago
parent a2bde5e016
commit ff3d38a515

@ -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;

@ -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;

@ -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,17 +208,21 @@ namespace NzbDrone.Core.Indexers.Torznab
IndexerFlags flags = 0;
var downloadFactor = TryGetFloatTorznabAttribute(item, "downloadvolumefactor", 1);
var uploadFactor = TryGetFloatTorznabAttribute(item, "uploadvolumefactor", 1);
if (uploadFactor == 2)
if (downloadFactor == 0.5)
{
flags |= IndexerFlags.G_DoubleUpload;
flags |= IndexerFlags.G_Halfleech;
}
if (downloadFactor == 0.5)
if (downloadFactor == 0.75)
{
flags |= IndexerFlags.G_Halfleech;
flags |= IndexerFlags.G_Freeleech25;
}
if (downloadFactor == 0.25)
{
flags |= IndexerFlags.G_Freeleech75;
}
if (downloadFactor == 0.0)
@ -226,6 +230,23 @@ namespace NzbDrone.Core.Indexers.Torznab
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…
Cancel
Save