mirror of
https://github.com/Prowlarr/Prowlarr
synced 2025-12-06 08:34:28 +01:00
New: Support Indexer Grab Redirects
This commit is contained in:
parent
a676eaeda5
commit
df8ef83e40
21 changed files with 109 additions and 15 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -12,6 +12,7 @@ src/**/[Oo]bj/
|
|||
*.sln.docstates
|
||||
.vs/
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# Build results
|
||||
*_i.c
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ class IndexerIndexRow extends Component {
|
|||
name,
|
||||
baseUrl,
|
||||
enable,
|
||||
redirect,
|
||||
tags,
|
||||
protocol,
|
||||
privacy,
|
||||
|
|
@ -114,6 +115,7 @@ class IndexerIndexRow extends Component {
|
|||
key={column.name}
|
||||
className={styles[column.name]}
|
||||
enabled={enable}
|
||||
redirect={redirect}
|
||||
status={status}
|
||||
longDateFormat={longDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
|
|
@ -258,6 +260,7 @@ IndexerIndexRow.propTypes = {
|
|||
priority: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
enable: PropTypes.bool.isRequired,
|
||||
redirect: PropTypes.bool.isRequired,
|
||||
status: PropTypes.object,
|
||||
capabilities: PropTypes.object.isRequired,
|
||||
added: PropTypes.string.isRequired,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ function IndexerStatusCell(props) {
|
|||
const {
|
||||
className,
|
||||
enabled,
|
||||
redirect,
|
||||
status,
|
||||
longDateFormat,
|
||||
timeFormat,
|
||||
|
|
@ -17,6 +18,9 @@ function IndexerStatusCell(props) {
|
|||
...otherProps
|
||||
} = props;
|
||||
|
||||
const enableKind = redirect ? kinds.WARNING : kinds.SUCCESS;
|
||||
const enableTitle = redirect ? 'Indexer is Enabled, Redirect is Enabled' : 'Indexer is Enabled';
|
||||
|
||||
return (
|
||||
<Component
|
||||
className={className}
|
||||
|
|
@ -25,9 +29,9 @@ function IndexerStatusCell(props) {
|
|||
{
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
kind={enabled ? kinds.SUCCESS : kinds.DEFAULT}
|
||||
kind={enabled ? enableKind : kinds.DEFAULT}
|
||||
name={enabled ? icons.CHECK : icons.BLACKLIST}
|
||||
title={enabled ? 'Indexer is Enabled' : 'Indexer is Disabled'}
|
||||
title={enabled ? enableTitle : 'Indexer is Disabled'}
|
||||
/>
|
||||
}
|
||||
{
|
||||
|
|
@ -46,6 +50,7 @@ function IndexerStatusCell(props) {
|
|||
IndexerStatusCell.propTypes = {
|
||||
className: PropTypes.string.isRequired,
|
||||
enabled: PropTypes.bool.isRequired,
|
||||
redirect: PropTypes.bool.isRequired,
|
||||
status: PropTypes.object,
|
||||
longDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ function EditIndexerModalContent(props) {
|
|||
implementationName,
|
||||
name,
|
||||
enable,
|
||||
redirect,
|
||||
supportsRss,
|
||||
supportsRedirect,
|
||||
fields,
|
||||
priority
|
||||
} = item;
|
||||
|
|
@ -90,6 +92,19 @@ function EditIndexerModalContent(props) {
|
|||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Redirect')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="redirect"
|
||||
helpText={'Redirect incoming download requests for indexer instead of Proxying using Prowlarr'}
|
||||
isDisabled={!supportsRedirect.value}
|
||||
{...redirect}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
fields.map((field) => {
|
||||
return (
|
||||
|
|
|
|||
30
src/NzbDrone.Core/Datastore/Migration/003_indexer_props.cs
Normal file
30
src/NzbDrone.Core/Datastore/Migration/003_indexer_props.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(3)]
|
||||
public class IndexerProps : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("Indexers")
|
||||
.AddColumn("Redirect").AsBoolean().NotNullable().WithDefaultValue(false);
|
||||
|
||||
Create.TableForModel("DownloadClients")
|
||||
.WithColumn("Enable").AsBoolean().NotNullable()
|
||||
.WithColumn("Name").AsString().NotNullable()
|
||||
.WithColumn("Implementation").AsString().NotNullable()
|
||||
.WithColumn("Settings").AsString().NotNullable()
|
||||
.WithColumn("ConfigContract").AsString().NotNullable()
|
||||
.WithColumn("Priority").AsInt32().WithDefaultValue(1);
|
||||
|
||||
Create.TableForModel("DownloadClientStatus")
|
||||
.WithColumn("ProviderId").AsInt32().NotNullable().Unique()
|
||||
.WithColumn("InitialFailure").AsDateTime().Nullable()
|
||||
.WithColumn("MostRecentFailure").AsDateTime().Nullable()
|
||||
.WithColumn("EscalationLevel").AsInt32().NotNullable()
|
||||
.WithColumn("DisabledTill").AsDateTime().Nullable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,7 @@ public static void Map()
|
|||
.Ignore(i => i.Privacy)
|
||||
.Ignore(i => i.SupportsRss)
|
||||
.Ignore(i => i.SupportsSearch)
|
||||
.Ignore(i => i.SupportsRedirect)
|
||||
.Ignore(i => i.Capabilities)
|
||||
.Ignore(d => d.Tags);
|
||||
|
||||
|
|
|
|||
|
|
@ -112,6 +112,8 @@ public void Handle(IndexerDownloadEvent message)
|
|||
|
||||
history.Data.Add("Successful", message.Successful.ToString());
|
||||
history.Data.Add("Source", message.Source ?? string.Empty);
|
||||
history.Data.Add("GrabMethod", message.Redirect ? "Proxy" : "Redirect");
|
||||
history.Data.Add("Title", message.Title);
|
||||
|
||||
_historyRepository.Insert(history);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ private IndexerDefinition GetDefinition(CardigannMetaDefinition definition)
|
|||
Privacy = definition.Type == "private" ? IndexerPrivacy.Private : IndexerPrivacy.Public,
|
||||
SupportsRss = SupportsRss,
|
||||
SupportsSearch = SupportsSearch,
|
||||
SupportsRedirect = SupportsRedirect,
|
||||
Capabilities = new IndexerCapabilities(),
|
||||
ExtraFields = settings
|
||||
};
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ protected Dictionary<string, object> GetBaseTemplateVariables()
|
|||
|
||||
if (setting.Type != "password")
|
||||
{
|
||||
_logger.Debug($"{name} got value {value.ToJson()}");
|
||||
_logger.Trace($"{name} got value {value.ToJson()}");
|
||||
}
|
||||
|
||||
if (setting.Type == "text" || setting.Type == "password")
|
||||
|
|
@ -236,7 +236,7 @@ protected Dictionary<string, object> GetBaseTemplateVariables()
|
|||
}
|
||||
else if (setting.Type == "select")
|
||||
{
|
||||
_logger.Debug($"Setting options: {setting.Options.ToJson()}");
|
||||
_logger.Trace($"Setting options: {setting.Options.ToJson()}");
|
||||
var sorted = setting.Options.OrderBy(x => x.Key).ToList();
|
||||
var selected = sorted[(int)(long)value];
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public class Newznab : HttpIndexerBase<NewznabSettings>
|
|||
public override string Name => "Newznab";
|
||||
public override string BaseUrl => GetBaseUrlFromSettings();
|
||||
public override bool FollowRedirect => true;
|
||||
public override bool SupportsRedirect => true;
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
|
@ -113,6 +114,7 @@ private IndexerDefinition GetDefinition(string name, NewznabSettings settings)
|
|||
Privacy = IndexerPrivacy.Private,
|
||||
SupportsRss = SupportsRss,
|
||||
SupportsSearch = SupportsSearch,
|
||||
SupportsRedirect = SupportsRedirect,
|
||||
Capabilities = Capabilities
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ private IndexerDefinition GetDefinition(string name, TorrentPotatoSettings setti
|
|||
Settings = settings,
|
||||
Protocol = DownloadProtocol.Torrent,
|
||||
SupportsRss = SupportsRss,
|
||||
SupportsSearch = SupportsSearch
|
||||
SupportsSearch = SupportsSearch,
|
||||
SupportsRedirect = SupportsRedirect
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ private IndexerDefinition GetDefinition(string name, TorznabSettings settings)
|
|||
Protocol = DownloadProtocol.Usenet,
|
||||
SupportsRss = SupportsRss,
|
||||
SupportsSearch = SupportsSearch,
|
||||
SupportsRedirect = SupportsRedirect,
|
||||
Capabilities = new IndexerCapabilities()
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ namespace NzbDrone.Core.Indexers
|
|||
{
|
||||
public interface IDownloadService
|
||||
{
|
||||
byte[] DownloadReport(string link, int indexerId, string source);
|
||||
byte[] DownloadReport(string link, int indexerId, string source, string title);
|
||||
void RecordRedirect(string link, int indexerId, string source, string title);
|
||||
}
|
||||
|
||||
public class DownloadService : IDownloadService
|
||||
|
|
@ -36,7 +37,7 @@ public DownloadService(IIndexerFactory indexerFactory,
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public byte[] DownloadReport(string link, int indexerId, string source)
|
||||
public byte[] DownloadReport(string link, int indexerId, string source, string title)
|
||||
{
|
||||
var url = new HttpUri(link);
|
||||
|
||||
|
|
@ -59,7 +60,7 @@ public byte[] DownloadReport(string link, int indexerId, string source)
|
|||
catch (ReleaseUnavailableException)
|
||||
{
|
||||
_logger.Trace("Release {0} no longer available on indexer.", link);
|
||||
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source));
|
||||
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, title));
|
||||
throw;
|
||||
}
|
||||
catch (ReleaseDownloadException ex)
|
||||
|
|
@ -74,12 +75,17 @@ public byte[] DownloadReport(string link, int indexerId, string source)
|
|||
_indexerStatusService.RecordFailure(indexerId);
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source));
|
||||
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, title));
|
||||
throw;
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source));
|
||||
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, title));
|
||||
return downloadedBytes;
|
||||
}
|
||||
|
||||
public void RecordRedirect(string link, int indexerId, string source, string title)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, true, source, title, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,16 @@ public class IndexerDownloadEvent : IEvent
|
|||
public int IndexerId { get; set; }
|
||||
public bool Successful { get; set; }
|
||||
public string Source { get; set; }
|
||||
public string Title { get; set; }
|
||||
public bool Redirect { get; set; }
|
||||
|
||||
public IndexerDownloadEvent(int indexerId, bool successful, string source)
|
||||
public IndexerDownloadEvent(int indexerId, bool successful, string source, string title, bool redirect = false)
|
||||
{
|
||||
IndexerId = indexerId;
|
||||
Successful = successful;
|
||||
Source = source;
|
||||
Title = title;
|
||||
Redirect = redirect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ public abstract class HttpIndexerBase<TSettings> : IndexerBase<TSettings>
|
|||
|
||||
public override bool SupportsRss => true;
|
||||
public override bool SupportsSearch => true;
|
||||
public override bool SupportsRedirect => false;
|
||||
|
||||
public override bool FollowRedirect => false;
|
||||
public override IndexerCapabilities Capabilities { get; protected set; }
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ public interface IIndexer : IProvider
|
|||
{
|
||||
bool SupportsRss { get; }
|
||||
bool SupportsSearch { get; }
|
||||
bool SupportsRedirect { get; }
|
||||
IndexerCapabilities Capabilities { get; }
|
||||
|
||||
string BaseUrl { get; }
|
||||
|
|
|
|||
|
|
@ -25,9 +25,11 @@ public abstract class IndexerBase<TSettings> : IIndexer
|
|||
public abstract DownloadProtocol Protocol { get; }
|
||||
public abstract IndexerPrivacy Privacy { get; }
|
||||
public int Priority { get; set; }
|
||||
public bool Redirect { get; set; }
|
||||
|
||||
public abstract bool SupportsRss { get; }
|
||||
public abstract bool SupportsSearch { get; }
|
||||
public abstract bool SupportsRedirect { get; }
|
||||
public abstract IndexerCapabilities Capabilities { get; protected set; }
|
||||
|
||||
public IndexerBase(IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
|
|
|
|||
|
|
@ -12,8 +12,10 @@ public class IndexerDefinition : ProviderDefinition
|
|||
public IndexerPrivacy Privacy { get; set; }
|
||||
public bool SupportsRss { get; set; }
|
||||
public bool SupportsSearch { get; set; }
|
||||
public bool SupportsRedirect { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
public int Priority { get; set; } = 25;
|
||||
public bool Redirect { get; set; }
|
||||
public DateTime Added { get; set; }
|
||||
|
||||
public IndexerStatus Status { get; set; }
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ public override void SetProviderCharacteristics(IIndexer provider, IndexerDefini
|
|||
definition.Protocol = provider.Protocol;
|
||||
definition.SupportsRss = provider.SupportsRss;
|
||||
definition.SupportsSearch = provider.SupportsSearch;
|
||||
definition.SupportsRedirect = provider.SupportsRedirect;
|
||||
|
||||
//We want to use the definition Caps and Privacy for Cardigann instead of the provider.
|
||||
if (definition.Implementation != typeof(Cardigann.Cardigann).Name)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System.Text;
|
||||
using Nancy;
|
||||
using Nancy.ModelBinding;
|
||||
using Nancy.Responses;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
|
|
@ -107,17 +108,26 @@ private object GetDownload(int id)
|
|||
throw new BadRequestException("Invalid Prowlarr link");
|
||||
}
|
||||
|
||||
file = WebUtility.UrlDecode(file);
|
||||
|
||||
if (indexer == null)
|
||||
{
|
||||
throw new NotFoundException("Indexer Not Found");
|
||||
}
|
||||
|
||||
var indexerInstance = _indexerFactory.GetInstance(indexer);
|
||||
|
||||
var source = UserAgentParser.ParseSource(Request.Headers.UserAgent);
|
||||
|
||||
var unprotectedlLink = _downloadMappingService.ConvertToNormalLink((string)link.Value);
|
||||
|
||||
// If Indexer is set to download via Redirect then just redirect to the link
|
||||
if (indexer.SupportsRedirect && indexer.Redirect)
|
||||
{
|
||||
_downloadService.RecordRedirect(unprotectedlLink, id, source, file);
|
||||
return Response.AsRedirect(unprotectedlLink, RedirectResponse.RedirectType.Temporary);
|
||||
}
|
||||
|
||||
var downloadBytes = Array.Empty<byte>();
|
||||
downloadBytes = _downloadService.DownloadReport(_downloadMappingService.ConvertToNormalLink(link), id, source);
|
||||
downloadBytes = _downloadService.DownloadReport(unprotectedlLink, id, source, file);
|
||||
|
||||
// handle magnet URLs
|
||||
if (downloadBytes.Length >= 7
|
||||
|
|
@ -130,7 +140,7 @@ private object GetDownload(int id)
|
|||
&& downloadBytes[6] == 0x3a)
|
||||
{
|
||||
var magnetUrl = Encoding.UTF8.GetString(downloadBytes);
|
||||
return Response.AsRedirect(magnetUrl);
|
||||
return Response.AsRedirect(magnetUrl, RedirectResponse.RedirectType.Temporary);
|
||||
}
|
||||
|
||||
var contentType = indexer.Protocol == DownloadProtocol.Torrent ? "application/x-bittorrent" : "application/x-nzb";
|
||||
|
|
|
|||
|
|
@ -14,8 +14,10 @@ public class IndexerResource : ProviderResource<IndexerResource>
|
|||
{
|
||||
public string BaseUrl { get; set; }
|
||||
public bool Enable { get; set; }
|
||||
public bool Redirect { get; set; }
|
||||
public bool SupportsRss { get; set; }
|
||||
public bool SupportsSearch { get; set; }
|
||||
public bool SupportsRedirect { get; set; }
|
||||
public DownloadProtocol Protocol { get; set; }
|
||||
public IndexerPrivacy Privacy { get; set; }
|
||||
public IndexerCapabilityResource Capabilities { get; set; }
|
||||
|
|
@ -61,8 +63,10 @@ public override IndexerResource ToResource(IndexerDefinition definition)
|
|||
|
||||
resource.BaseUrl = definition.BaseUrl;
|
||||
resource.Enable = definition.Enable;
|
||||
resource.Redirect = definition.Redirect;
|
||||
resource.SupportsRss = definition.SupportsRss;
|
||||
resource.SupportsSearch = definition.SupportsSearch;
|
||||
resource.SupportsRedirect = definition.SupportsRedirect;
|
||||
resource.Capabilities = definition.Capabilities.ToResource();
|
||||
resource.Protocol = definition.Protocol;
|
||||
resource.Privacy = definition.Privacy;
|
||||
|
|
@ -100,6 +104,7 @@ public override IndexerDefinition ToModel(IndexerResource resource)
|
|||
}
|
||||
|
||||
definition.Enable = resource.Enable;
|
||||
definition.Redirect = resource.Redirect;
|
||||
definition.BaseUrl = resource.BaseUrl;
|
||||
definition.Priority = resource.Priority;
|
||||
definition.Privacy = resource.Privacy;
|
||||
|
|
|
|||
Loading…
Reference in a new issue