From 76c3283c05f61a3d6b14fe4675f66b374511cc6b Mon Sep 17 00:00:00 2001 From: ta264 Date: Wed, 28 Apr 2021 21:30:02 +0100 Subject: [PATCH] New: Move newznab endpoint to /{id}/api --- .../Indexer/Info/IndexerInfoModalContent.js | 2 +- .../Applications/ApplicationBase.cs | 2 +- .../Applications/Lidarr/Lidarr.cs | 4 +- .../Applications/Radarr/Radarr.cs | 4 +- .../Applications/Readarr/Readarr.cs | 4 +- .../Applications/Sonarr/Sonarr.cs | 4 +- .../Indexers/DownloadMappingService.cs | 2 +- .../Indexers/IndexerController.cs | 120 +--------------- .../Indexers/NewznabController.cs | 136 ++++++++++++++++++ 9 files changed, 148 insertions(+), 130 deletions(-) create mode 100644 src/Prowlarr.Api.V1/Indexers/NewznabController.cs diff --git a/frontend/src/Indexer/Info/IndexerInfoModalContent.js b/frontend/src/Indexer/Info/IndexerInfoModalContent.js index 3788717b4..cb2a93470 100644 --- a/frontend/src/Indexer/Info/IndexerInfoModalContent.js +++ b/frontend/src/Indexer/Info/IndexerInfoModalContent.js @@ -59,7 +59,7 @@ function IndexerInfoModalContent(props) { {protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url - {`${window.Prowlarr.apiRoot}/indexer/${id}/newznab`} + {`${window.Prowlarr.apiRoot}/${id}/api`} diff --git a/src/NzbDrone.Core/Applications/ApplicationBase.cs b/src/NzbDrone.Core/Applications/ApplicationBase.cs index 9772ce1f0..e38854462 100644 --- a/src/NzbDrone.Core/Applications/ApplicationBase.cs +++ b/src/NzbDrone.Core/Applications/ApplicationBase.cs @@ -14,7 +14,7 @@ namespace NzbDrone.Core.Applications protected readonly IAppIndexerMapService _appIndexerMapService; protected readonly Logger _logger; - protected static readonly Regex AppIndexerRegex = new Regex(@"api\/v\d*\/indexer\/(?\d*)", + protected static readonly Regex AppIndexerRegex = new Regex(@"(?\d*)/api", RegexOptions.IgnoreCase | RegexOptions.Compiled); public abstract string Name { get; } diff --git a/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs b/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs index 661d3ef63..31269af81 100644 --- a/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs +++ b/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs @@ -139,8 +139,8 @@ namespace NzbDrone.Core.Applications.Lidarr Fields = schema.Fields, }; - lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/api/v1/indexer/{indexer.Id}/"; - lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/newznab"; + lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/"; + 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 == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())); diff --git a/src/NzbDrone.Core/Applications/Radarr/Radarr.cs b/src/NzbDrone.Core/Applications/Radarr/Radarr.cs index ce09b4cf3..2e4772325 100644 --- a/src/NzbDrone.Core/Applications/Radarr/Radarr.cs +++ b/src/NzbDrone.Core/Applications/Radarr/Radarr.cs @@ -139,8 +139,8 @@ namespace NzbDrone.Core.Applications.Radarr Fields = schema.Fields, }; - radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/api/v1/indexer/{indexer.Id}/"; - radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/newznab"; + radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/"; + 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 == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())); diff --git a/src/NzbDrone.Core/Applications/Readarr/Readarr.cs b/src/NzbDrone.Core/Applications/Readarr/Readarr.cs index 6ffa2e363..026c0a075 100644 --- a/src/NzbDrone.Core/Applications/Readarr/Readarr.cs +++ b/src/NzbDrone.Core/Applications/Readarr/Readarr.cs @@ -139,8 +139,8 @@ namespace NzbDrone.Core.Applications.Readarr Fields = schema.Fields, }; - readarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/api/v1/indexer/{indexer.Id}/"; - readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/newznab"; + readarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/"; + 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 == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())); diff --git a/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs b/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs index 0a189f02c..6583610bb 100644 --- a/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs +++ b/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs @@ -139,8 +139,8 @@ namespace NzbDrone.Core.Applications.Sonarr Fields = schema.Fields, }; - sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/api/v1/indexer/{indexer.Id}/"; - sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/newznab"; + sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/{indexer.Id}/"; + 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 == "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())); diff --git a/src/NzbDrone.Core/Indexers/DownloadMappingService.cs b/src/NzbDrone.Core/Indexers/DownloadMappingService.cs index 956ed4871..2b34684ef 100644 --- a/src/NzbDrone.Core/Indexers/DownloadMappingService.cs +++ b/src/NzbDrone.Core/Indexers/DownloadMappingService.cs @@ -37,7 +37,7 @@ namespace NzbDrone.Core.Indexers var encryptedLink = _protectionService.Protect(link.ToString()); var encodedLink = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(encryptedLink)); 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); } diff --git a/src/Prowlarr.Api.V1/Indexers/IndexerController.cs b/src/Prowlarr.Api.V1/Indexers/IndexerController.cs index 521a37109..48adfcf52 100644 --- a/src/Prowlarr.Api.V1/Indexers/IndexerController.cs +++ b/src/Prowlarr.Api.V1/Indexers/IndexerController.cs @@ -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.IndexerSearch; -using NzbDrone.Core.Parser; using Prowlarr.Http; -using Prowlarr.Http.Extensions; -using Prowlarr.Http.REST; namespace Prowlarr.Api.V1.Indexers { [V1ApiController] public class IndexerController : ProviderControllerBase { - private IIndexerFactory _indexerFactory { get; set; } - 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) + public IndexerController(IndexerFactory indexerFactory, IndexerResourceMapper resourceMapper) : base(indexerFactory, "indexer", resourceMapper) { - _indexerFactory = indexerFactory; - _nzbSearchService = nzbSearchService; - _downloadMappingService = downloadMappingService; - _downloadService = downloadService; } protected override void Validate(IndexerDefinition definition, bool includeWarnings) @@ -41,102 +20,5 @@ namespace Prowlarr.Api.V1.Indexers base.Validate(definition, includeWarnings); } - - [HttpGet("{id:int}/newznab")] - public async Task 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 { 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 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(); - 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); - } } } diff --git a/src/Prowlarr.Api.V1/Indexers/NewznabController.cs b/src/Prowlarr.Api.V1/Indexers/NewznabController.cs new file mode 100644 index 000000000..2ab124f0f --- /dev/null +++ b/src/Prowlarr.Api.V1/Indexers/NewznabController.cs @@ -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 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 { 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 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(); + 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); + } + } +}