mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
New: Move newznab endpoint to /{id}/api
This commit is contained in:
@@ -59,7 +59,7 @@ function IndexerInfoModalContent(props) {
|
|||||||
|
|
||||||
<DescriptionListItemTitle>{protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url</DescriptionListItemTitle>
|
<DescriptionListItemTitle>{protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url</DescriptionListItemTitle>
|
||||||
<DescriptionListItemDescription>
|
<DescriptionListItemDescription>
|
||||||
{`${window.Prowlarr.apiRoot}/indexer/${id}/newznab`}
|
{`${window.Prowlarr.apiRoot}/${id}/api`}
|
||||||
</DescriptionListItemDescription>
|
</DescriptionListItemDescription>
|
||||||
|
|
||||||
</DescriptionList>
|
</DescriptionList>
|
||||||
|
@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Applications
|
|||||||
protected readonly IAppIndexerMapService _appIndexerMapService;
|
protected readonly IAppIndexerMapService _appIndexerMapService;
|
||||||
protected readonly Logger _logger;
|
protected readonly Logger _logger;
|
||||||
|
|
||||||
protected static readonly Regex AppIndexerRegex = new Regex(@"api\/v\d*\/indexer\/(?<indexer>\d*)",
|
protected static readonly Regex AppIndexerRegex = new Regex(@"(?<indexer>\d*)/api",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
public abstract string Name { get; }
|
public abstract string Name { get; }
|
||||||
|
@@ -139,8 +139,8 @@ namespace NzbDrone.Core.Applications.Lidarr
|
|||||||
Fields = schema.Fields,
|
Fields = schema.Fields,
|
||||||
};
|
};
|
||||||
|
|
||||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/api/v1/indexer/{indexer.Id}/";
|
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
|
||||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/newznab";
|
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||||
|
|
||||||
|
@@ -139,8 +139,8 @@ namespace NzbDrone.Core.Applications.Radarr
|
|||||||
Fields = schema.Fields,
|
Fields = schema.Fields,
|
||||||
};
|
};
|
||||||
|
|
||||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/api/v1/indexer/{indexer.Id}/";
|
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
|
||||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/newznab";
|
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||||
|
|
||||||
|
@@ -139,8 +139,8 @@ namespace NzbDrone.Core.Applications.Readarr
|
|||||||
Fields = schema.Fields,
|
Fields = schema.Fields,
|
||||||
};
|
};
|
||||||
|
|
||||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/api/v1/indexer/{indexer.Id}/";
|
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
|
||||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/newznab";
|
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||||
|
|
||||||
|
@@ -139,8 +139,8 @@ namespace NzbDrone.Core.Applications.Sonarr
|
|||||||
Fields = schema.Fields,
|
Fields = schema.Fields,
|
||||||
};
|
};
|
||||||
|
|
||||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/api/v1/indexer/{indexer.Id}/";
|
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/";
|
||||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/newznab";
|
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "animeCategories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "animeCategories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()));
|
||||||
|
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Indexers
|
|||||||
var encryptedLink = _protectionService.Protect(link.ToString());
|
var encryptedLink = _protectionService.Protect(link.ToString());
|
||||||
var encodedLink = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(encryptedLink));
|
var encodedLink = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(encryptedLink));
|
||||||
var urlEncodedFile = WebUtility.UrlEncode(file);
|
var urlEncodedFile = WebUtility.UrlEncode(file);
|
||||||
var proxyLink = $"{serverUrl}{urlBase}/api/v1/indexer/{indexerId}/download?apikey={_configFileProvider.ApiKey}&link={encodedLink}&file={urlEncodedFile}";
|
var proxyLink = $"{serverUrl}{urlBase}/{indexerId}/download?apikey={_configFileProvider.ApiKey}&link={encodedLink}&file={urlEncodedFile}";
|
||||||
return new Uri(proxyLink);
|
return new Uri(proxyLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,35 +1,14 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Core.Download;
|
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.IndexerSearch;
|
|
||||||
using NzbDrone.Core.Parser;
|
|
||||||
using Prowlarr.Http;
|
using Prowlarr.Http;
|
||||||
using Prowlarr.Http.Extensions;
|
|
||||||
using Prowlarr.Http.REST;
|
|
||||||
|
|
||||||
namespace Prowlarr.Api.V1.Indexers
|
namespace Prowlarr.Api.V1.Indexers
|
||||||
{
|
{
|
||||||
[V1ApiController]
|
[V1ApiController]
|
||||||
public class IndexerController : ProviderControllerBase<IndexerResource, IIndexer, IndexerDefinition>
|
public class IndexerController : ProviderControllerBase<IndexerResource, IIndexer, IndexerDefinition>
|
||||||
{
|
{
|
||||||
private IIndexerFactory _indexerFactory { get; set; }
|
public IndexerController(IndexerFactory indexerFactory, IndexerResourceMapper resourceMapper)
|
||||||
private ISearchForNzb _nzbSearchService { get; set; }
|
|
||||||
private IDownloadMappingService _downloadMappingService { get; set; }
|
|
||||||
private IDownloadService _downloadService { get; set; }
|
|
||||||
|
|
||||||
public IndexerController(IndexerFactory indexerFactory, ISearchForNzb nzbSearchService, IDownloadMappingService downloadMappingService, IDownloadService downloadService, IndexerResourceMapper resourceMapper)
|
|
||||||
: base(indexerFactory, "indexer", resourceMapper)
|
: base(indexerFactory, "indexer", resourceMapper)
|
||||||
{
|
{
|
||||||
_indexerFactory = indexerFactory;
|
|
||||||
_nzbSearchService = nzbSearchService;
|
|
||||||
_downloadMappingService = downloadMappingService;
|
|
||||||
_downloadService = downloadService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Validate(IndexerDefinition definition, bool includeWarnings)
|
protected override void Validate(IndexerDefinition definition, bool includeWarnings)
|
||||||
@@ -41,102 +20,5 @@ namespace Prowlarr.Api.V1.Indexers
|
|||||||
|
|
||||||
base.Validate(definition, includeWarnings);
|
base.Validate(definition, includeWarnings);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id:int}/newznab")]
|
|
||||||
public async Task<IActionResult> GetNewznabResponse(int id, [FromQuery] NewznabRequest request)
|
|
||||||
{
|
|
||||||
var requestType = request.t;
|
|
||||||
request.source = UserAgentParser.ParseSource(Request.Headers["User-Agent"]);
|
|
||||||
request.server = Request.GetServerUrl();
|
|
||||||
|
|
||||||
if (requestType.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
throw new BadRequestException("Missing Function Parameter");
|
|
||||||
}
|
|
||||||
|
|
||||||
var indexer = _indexerFactory.Get(id);
|
|
||||||
|
|
||||||
if (indexer == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException("Indexer Not Found");
|
|
||||||
}
|
|
||||||
|
|
||||||
var indexerInstance = _indexerFactory.GetInstance(indexer);
|
|
||||||
|
|
||||||
switch (requestType)
|
|
||||||
{
|
|
||||||
case "caps":
|
|
||||||
var caps = indexerInstance.GetCapabilities();
|
|
||||||
return Content(caps.ToXml(), "application/rss+xml");
|
|
||||||
case "search":
|
|
||||||
case "tvsearch":
|
|
||||||
case "music":
|
|
||||||
case "book":
|
|
||||||
case "movie":
|
|
||||||
var results = await _nzbSearchService.Search(request, new List<int> { indexer.Id }, false);
|
|
||||||
|
|
||||||
foreach (var result in results.Releases)
|
|
||||||
{
|
|
||||||
result.DownloadUrl = _downloadMappingService.ConvertToProxyLink(new Uri(result.DownloadUrl), request.server, indexer.Id, result.Title).ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Content(results.ToXml(indexerInstance.Protocol), "application/rss+xml");
|
|
||||||
default:
|
|
||||||
throw new BadRequestException("Function Not Available");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/download")]
|
|
||||||
public async Task<object> GetDownload(int id, string link, string file)
|
|
||||||
{
|
|
||||||
var indexerDef = _indexerFactory.Get(id);
|
|
||||||
var indexer = _indexerFactory.GetInstance(indexerDef);
|
|
||||||
|
|
||||||
if (link.IsNullOrWhiteSpace() || file.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
throw new BadRequestException("Invalid Prowlarr link");
|
|
||||||
}
|
|
||||||
|
|
||||||
file = WebUtility.UrlDecode(file);
|
|
||||||
|
|
||||||
if (indexer == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException("Indexer Not Found");
|
|
||||||
}
|
|
||||||
|
|
||||||
var source = UserAgentParser.ParseSource(Request.Headers["User-Agent"]);
|
|
||||||
|
|
||||||
var unprotectedlLink = _downloadMappingService.ConvertToNormalLink(link);
|
|
||||||
|
|
||||||
// If Indexer is set to download via Redirect then just redirect to the link
|
|
||||||
if (indexer.SupportsRedirect && indexerDef.Redirect)
|
|
||||||
{
|
|
||||||
_downloadService.RecordRedirect(unprotectedlLink, id, source, file);
|
|
||||||
return RedirectPermanent(unprotectedlLink);
|
|
||||||
}
|
|
||||||
|
|
||||||
var downloadBytes = Array.Empty<byte>();
|
|
||||||
downloadBytes = await _downloadService.DownloadReport(unprotectedlLink, id, source, file);
|
|
||||||
|
|
||||||
// handle magnet URLs
|
|
||||||
if (downloadBytes.Length >= 7
|
|
||||||
&& downloadBytes[0] == 0x6d
|
|
||||||
&& downloadBytes[1] == 0x61
|
|
||||||
&& downloadBytes[2] == 0x67
|
|
||||||
&& downloadBytes[3] == 0x6e
|
|
||||||
&& downloadBytes[4] == 0x65
|
|
||||||
&& downloadBytes[5] == 0x74
|
|
||||||
&& downloadBytes[6] == 0x3a)
|
|
||||||
{
|
|
||||||
var magnetUrl = Encoding.UTF8.GetString(downloadBytes);
|
|
||||||
return RedirectPermanent(magnetUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
var contentType = indexer.Protocol == DownloadProtocol.Torrent ? "application/x-bittorrent" : "application/x-nzb";
|
|
||||||
var extension = indexer.Protocol == DownloadProtocol.Torrent ? "torrent" : "nzb";
|
|
||||||
var filename = $"{file}.{extension}";
|
|
||||||
|
|
||||||
return File(downloadBytes, contentType, filename);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
136
src/Prowlarr.Api.V1/Indexers/NewznabController.cs
Normal file
136
src/Prowlarr.Api.V1/Indexers/NewznabController.cs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Cors;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.IndexerSearch;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
using Prowlarr.Http.Extensions;
|
||||||
|
using Prowlarr.Http.REST;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.V1.Indexers
|
||||||
|
{
|
||||||
|
[Route("")]
|
||||||
|
[EnableCors("ApiCorsPolicy")]
|
||||||
|
[ApiController]
|
||||||
|
public class NewznabController : Controller
|
||||||
|
{
|
||||||
|
private IIndexerFactory _indexerFactory { get; set; }
|
||||||
|
private ISearchForNzb _nzbSearchService { get; set; }
|
||||||
|
private IDownloadMappingService _downloadMappingService { get; set; }
|
||||||
|
private IDownloadService _downloadService { get; set; }
|
||||||
|
|
||||||
|
public NewznabController(IndexerFactory indexerFactory,
|
||||||
|
ISearchForNzb nzbSearchService,
|
||||||
|
IDownloadMappingService downloadMappingService,
|
||||||
|
IDownloadService downloadService)
|
||||||
|
{
|
||||||
|
_indexerFactory = indexerFactory;
|
||||||
|
_nzbSearchService = nzbSearchService;
|
||||||
|
_downloadMappingService = downloadMappingService;
|
||||||
|
_downloadService = downloadService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id:int}/api")]
|
||||||
|
public async Task<IActionResult> GetNewznabResponse(int id, [FromQuery] NewznabRequest request)
|
||||||
|
{
|
||||||
|
var requestType = request.t;
|
||||||
|
request.source = UserAgentParser.ParseSource(Request.Headers["User-Agent"]);
|
||||||
|
request.server = Request.GetServerUrl();
|
||||||
|
|
||||||
|
if (requestType.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Missing Function Parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
var indexer = _indexerFactory.Get(id);
|
||||||
|
|
||||||
|
if (indexer == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException("Indexer Not Found");
|
||||||
|
}
|
||||||
|
|
||||||
|
var indexerInstance = _indexerFactory.GetInstance(indexer);
|
||||||
|
|
||||||
|
switch (requestType)
|
||||||
|
{
|
||||||
|
case "caps":
|
||||||
|
var caps = indexerInstance.GetCapabilities();
|
||||||
|
return Content(caps.ToXml(), "application/rss+xml");
|
||||||
|
case "search":
|
||||||
|
case "tvsearch":
|
||||||
|
case "music":
|
||||||
|
case "book":
|
||||||
|
case "movie":
|
||||||
|
var results = await _nzbSearchService.Search(request, new List<int> { indexer.Id }, false);
|
||||||
|
|
||||||
|
foreach (var result in results.Releases)
|
||||||
|
{
|
||||||
|
result.DownloadUrl = _downloadMappingService.ConvertToProxyLink(new Uri(result.DownloadUrl), request.server, indexer.Id, result.Title).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Content(results.ToXml(indexerInstance.Protocol), "application/rss+xml");
|
||||||
|
default:
|
||||||
|
throw new BadRequestException("Function Not Available");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id:int}/download")]
|
||||||
|
public async Task<object> GetDownload(int id, string link, string file)
|
||||||
|
{
|
||||||
|
var indexerDef = _indexerFactory.Get(id);
|
||||||
|
var indexer = _indexerFactory.GetInstance(indexerDef);
|
||||||
|
|
||||||
|
if (link.IsNullOrWhiteSpace() || file.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Invalid Prowlarr link");
|
||||||
|
}
|
||||||
|
|
||||||
|
file = WebUtility.UrlDecode(file);
|
||||||
|
|
||||||
|
if (indexer == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException("Indexer Not Found");
|
||||||
|
}
|
||||||
|
|
||||||
|
var source = UserAgentParser.ParseSource(Request.Headers["User-Agent"]);
|
||||||
|
|
||||||
|
var unprotectedlLink = _downloadMappingService.ConvertToNormalLink(link);
|
||||||
|
|
||||||
|
// If Indexer is set to download via Redirect then just redirect to the link
|
||||||
|
if (indexer.SupportsRedirect && indexerDef.Redirect)
|
||||||
|
{
|
||||||
|
_downloadService.RecordRedirect(unprotectedlLink, id, source, file);
|
||||||
|
return RedirectPermanent(unprotectedlLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
var downloadBytes = Array.Empty<byte>();
|
||||||
|
downloadBytes = await _downloadService.DownloadReport(unprotectedlLink, id, source, file);
|
||||||
|
|
||||||
|
// handle magnet URLs
|
||||||
|
if (downloadBytes.Length >= 7
|
||||||
|
&& downloadBytes[0] == 0x6d
|
||||||
|
&& downloadBytes[1] == 0x61
|
||||||
|
&& downloadBytes[2] == 0x67
|
||||||
|
&& downloadBytes[3] == 0x6e
|
||||||
|
&& downloadBytes[4] == 0x65
|
||||||
|
&& downloadBytes[5] == 0x74
|
||||||
|
&& downloadBytes[6] == 0x3a)
|
||||||
|
{
|
||||||
|
var magnetUrl = Encoding.UTF8.GetString(downloadBytes);
|
||||||
|
return RedirectPermanent(magnetUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentType = indexer.Protocol == DownloadProtocol.Torrent ? "application/x-bittorrent" : "application/x-nzb";
|
||||||
|
var extension = indexer.Protocol == DownloadProtocol.Torrent ? "torrent" : "nzb";
|
||||||
|
var filename = $"{file}.{extension}";
|
||||||
|
|
||||||
|
return File(downloadBytes, contentType, filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user