Newznab Work

pull/6/head
Qstick 4 years ago
parent 8e72c7153d
commit b586b6ac1c

@ -3,17 +3,21 @@ import React from 'react';
import Label from 'Components/Label'; import Label from 'Components/Label';
function CategoryLabel({ categories }) { function CategoryLabel({ categories }) {
let catName = ''; const sortedCategories = categories.sort((c) => c.id);
if (categories && categories.length > 0) {
catName = categories[0].name;
}
return ( return (
<Label> <span>
{catName} {
sortedCategories.map((category) => {
return (
<Label key={category.name}>
{category.name}
</Label> </Label>
); );
})
}
</span>
);
} }
CategoryLabel.propTypes = { CategoryLabel.propTypes = {

@ -10,7 +10,12 @@
flex: 4 0 110px; flex: 4 0 110px;
} }
.category, .category {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
flex: 0 0 110px;
}
.indexer { .indexer {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css'; composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';

@ -17,7 +17,12 @@
flex: 4 0 110px; flex: 4 0 110px;
} }
.category, .category {
composes: cell;
flex: 0 0 110px;
}
.indexer { .indexer {
composes: cell; composes: cell;

@ -1,6 +1,9 @@
import React from 'react'; import React, { Fragment } from 'react';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody'; import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import { icons } from 'Helpers/Props';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import ApplicationsConnector from './Applications/ApplicationsConnector'; import ApplicationsConnector from './Applications/ApplicationsConnector';
@ -10,6 +13,21 @@ function ApplicationSettings() {
<PageContent title={translate('Applications')}> <PageContent title={translate('Applications')}>
<SettingsToolbarConnector <SettingsToolbarConnector
showSave={false} showSave={false}
additionalButtons={
<Fragment>
<PageToolbarSeparator />
<PageToolbarButton
label={translate('SyncAppIndexers')}
iconName={icons.REFRESH}
/>
<PageToolbarButton
label={translate('TestAllApps')}
iconName={icons.TEST}
/>
</Fragment>
}
/> />
<PageContentBody> <PageContentBody>

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.Applications
{
public class ApplicationIndexerSyncCommand : Command
{
public override bool SendUpdatesToClient => true;
public override string CompletionMessage => null;
}
}

@ -1,11 +1,16 @@
using NLog; using NLog;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider.Events; using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.Applications namespace NzbDrone.Core.Applications
{ {
public class ApplicationService : IHandle<ProviderAddedEvent<IIndexer>>, IHandle<ProviderDeletedEvent<IIndexer>>, IHandle<ProviderAddedEvent<IApplication>>, IHandle<ProviderUpdatedEvent<IIndexer>> public class ApplicationService : IHandleAsync<ProviderAddedEvent<IIndexer>>,
IHandleAsync<ProviderDeletedEvent<IIndexer>>,
IHandleAsync<ProviderAddedEvent<IApplication>>,
IHandleAsync<ProviderUpdatedEvent<IIndexer>>,
IExecute<ApplicationIndexerSyncCommand>
{ {
private readonly IApplicationFactory _applicationsFactory; private readonly IApplicationFactory _applicationsFactory;
private readonly Logger _logger; private readonly Logger _logger;
@ -17,7 +22,7 @@ namespace NzbDrone.Core.Applications
} }
// Sync Indexers on App Add if Sync Enabled // Sync Indexers on App Add if Sync Enabled
public void Handle(ProviderAddedEvent<IApplication> message) public void HandleAsync(ProviderAddedEvent<IApplication> message)
{ {
var appDefinition = (ApplicationDefinition)message.Definition; var appDefinition = (ApplicationDefinition)message.Definition;
@ -29,7 +34,7 @@ namespace NzbDrone.Core.Applications
} }
} }
public void Handle(ProviderAddedEvent<IIndexer> message) public void HandleAsync(ProviderAddedEvent<IIndexer> message)
{ {
var enabledApps = _applicationsFactory.GetAvailableProviders(); var enabledApps = _applicationsFactory.GetAvailableProviders();
@ -40,7 +45,7 @@ namespace NzbDrone.Core.Applications
} }
} }
public void Handle(ProviderDeletedEvent<IIndexer> message) public void HandleAsync(ProviderDeletedEvent<IIndexer> message)
{ {
var enabledApps = _applicationsFactory.GetAvailableProviders(); var enabledApps = _applicationsFactory.GetAvailableProviders();
@ -51,7 +56,7 @@ namespace NzbDrone.Core.Applications
} }
} }
public void Handle(ProviderUpdatedEvent<IIndexer> message) public void HandleAsync(ProviderUpdatedEvent<IIndexer> message)
{ {
var enabledApps = _applicationsFactory.GetAvailableProviders(); var enabledApps = _applicationsFactory.GetAvailableProviders();
@ -61,5 +66,15 @@ namespace NzbDrone.Core.Applications
app.UpdateIndexer((IndexerDefinition)message.Definition); app.UpdateIndexer((IndexerDefinition)message.Definition);
} }
} }
public void Execute(ApplicationIndexerSyncCommand message)
{
var enabledApps = _applicationsFactory.GetAvailableProviders();
foreach (var app in enabledApps)
{
app.SyncIndexers();
}
}
} }
} }

@ -4,7 +4,6 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
{ {
public string ImdbId { get; set; } public string ImdbId { get; set; }
public int? TmdbId { get; set; } public int? TmdbId { get; set; }
public int? Year { get; set; }
public int? TraktId { get; set; } public int? TraktId { get; set; }
} }
} }

@ -60,7 +60,6 @@ namespace NzbDrone.Core.IndexerSearch
searchSpec.ImdbId = request.imdbid; searchSpec.ImdbId = request.imdbid;
searchSpec.TmdbId = request.tmdbid; searchSpec.TmdbId = request.tmdbid;
searchSpec.TraktId = request.traktid; searchSpec.TraktId = request.traktid;
searchSpec.Year = request.year;
return new NewznabResults { Releases = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec) }; return new NewznabResults { Releases = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec) };
} }

@ -60,7 +60,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
{ {
foreach (var category in _definition.Caps.Categories) foreach (var category in _definition.Caps.Categories)
{ {
var cat = TorznabCatType.GetCatByName(category.Value); var cat = NewznabStandardCategory.GetCatByName(category.Value);
if (cat == null) if (cat == null)
{ {
_logger.Error(string.Format("CardigannIndexer ({0}): invalid Torznab category for id {1}: {2}", _definition.Id, category.Key, category.Value)); _logger.Error(string.Format("CardigannIndexer ({0}): invalid Torznab category for id {1}: {2}", _definition.Id, category.Key, category.Value));
@ -79,7 +79,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
if (categorymapping.cat != null) if (categorymapping.cat != null)
{ {
torznabCat = TorznabCatType.GetCatByName(categorymapping.cat); torznabCat = NewznabStandardCategory.GetCatByName(categorymapping.cat);
if (torznabCat == null) if (torznabCat == null)
{ {
_logger.Error(string.Format("CardigannIndexer ({0}): invalid Torznab category for id {1}: {2}", _definition.Id, categorymapping.id, categorymapping.cat)); _logger.Error(string.Format("CardigannIndexer ({0}): invalid Torznab category for id {1}: {2}", _definition.Id, categorymapping.id, categorymapping.cat));
@ -262,7 +262,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
.Where(m => .Where(m =>
!string.IsNullOrWhiteSpace(m.TrackerCategory) && !string.IsNullOrWhiteSpace(m.TrackerCategory) &&
string.Equals(m.TrackerCategory, input, StringComparison.InvariantCultureIgnoreCase)) string.Equals(m.TrackerCategory, input, StringComparison.InvariantCultureIgnoreCase))
.Select(c => TorznabCatType.AllCats.FirstOrDefault(n => n.Id == c.NewzNabCategory) ?? new IndexerCategory { Id = c.NewzNabCategory }) .Select(c => NewznabStandardCategory.AllCats.FirstOrDefault(n => n.Id == c.NewzNabCategory) ?? new IndexerCategory { Id = c.NewzNabCategory })
.ToList(); .ToList();
return cats; return cats;
} }

@ -29,9 +29,9 @@ namespace NzbDrone.Core.Indexers.Cardigann
var variables = GetQueryVariableDefaults(searchCriteria); var variables = GetQueryVariableDefaults(searchCriteria);
variables[".Query.Movie"] = null; variables[".Query.Movie"] = null;
variables[".Query.Year"] = searchCriteria.Year; variables[".Query.Year"] = null;
variables[".Query.IMDBID"] = searchCriteria.ImdbId; variables[".Query.IMDBID"] = searchCriteria.ImdbId;
variables[".Query.IMDBIDShort"] = searchCriteria.ImdbId.Replace("tt", ""); variables[".Query.IMDBIDShort"] = searchCriteria.ImdbId?.TrimStart('t') ?? null;
variables[".Query.TMDBID"] = searchCriteria.TmdbId; variables[".Query.TMDBID"] = searchCriteria.TmdbId;
variables[".Query.TraktID"] = searchCriteria.TraktId; variables[".Query.TraktID"] = searchCriteria.TraktId;

@ -20,7 +20,7 @@ namespace NzbDrone.Core.Indexers.Newznab
public override DownloadProtocol Protocol => DownloadProtocol.Usenet; public override DownloadProtocol Protocol => DownloadProtocol.Usenet;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private; public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities { get => new IndexerCapabilities(); protected set => base.Capabilities = value; } public override IndexerCapabilities Capabilities { get => GetCapabilitiesFromSettings(); protected set => base.Capabilities = value; }
public override int PageSize => _capabilitiesProvider.GetCapabilities(Settings).LimitsDefault.Value; public override int PageSize => _capabilitiesProvider.GetCapabilities(Settings).LimitsDefault.Value;
@ -38,6 +38,29 @@ namespace NzbDrone.Core.Indexers.Newznab
return new NewznabRssParser(Settings); return new NewznabRssParser(Settings);
} }
public IndexerCapabilities GetCapabilitiesFromSettings()
{
var caps = new IndexerCapabilities();
if (Definition == null || Settings == null || Settings.Categories == null)
{
return caps;
}
foreach (var category in Settings.Categories)
{
caps.Categories.AddCategoryMapping(category.Name, category);
}
return caps;
}
public override IndexerCapabilities GetCapabilities()
{
// Newznab uses different Caps per site, so we need to cache them to db on first indexer add to prevent issues with loading UI and pulling caps every time.
return _capabilitiesProvider.GetCapabilities(Settings);
}
public override IEnumerable<ProviderDefinition> DefaultDefinitions public override IEnumerable<ProviderDefinition> DefaultDefinitions
{ {
get get

@ -206,12 +206,15 @@ namespace NzbDrone.Core.Indexers.Newznab
foreach (var xmlSubcat in xmlCategory.Elements("subcat")) foreach (var xmlSubcat in xmlCategory.Elements("subcat"))
{ {
cat.SubCategories.Add(new IndexerCategory var subCat = new IndexerCategory
{ {
Id = int.Parse(xmlSubcat.Attribute("id").Value), Id = int.Parse(xmlSubcat.Attribute("id").Value),
Name = xmlSubcat.Attribute("name").Value, Name = xmlSubcat.Attribute("name").Value,
Description = xmlSubcat.Attribute("description") != null ? xmlSubcat.Attribute("description").Value : string.Empty Description = xmlSubcat.Attribute("description") != null ? xmlSubcat.Attribute("description").Value : string.Empty
}); };
cat.SubCategories.Add(subCat);
capabilities.Categories.AddCategoryMapping(subCat.Name, subCat);
} }
capabilities.Categories.AddCategoryMapping(cat.Name, cat); capabilities.Categories.AddCategoryMapping(cat.Name, cat);

@ -22,55 +22,35 @@ namespace NzbDrone.Core.Indexers.Newznab
PageSize = 100; PageSize = 100;
} }
private bool SupportsSearch public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
get
{ {
var capabilities = _capabilitiesProvider.GetCapabilities(Settings); var capabilities = _capabilitiesProvider.GetCapabilities(Settings);
return capabilities.SearchParams != null && var pageableRequests = new IndexerPageableRequestChain();
capabilities.SearchParams.Contains(SearchParam.Q); var parameters = string.Empty;
}
}
private bool SupportsImdbSearch if (searchCriteria.TmdbId.HasValue && capabilities.MovieSearchTmdbAvailable)
{
get
{ {
var capabilities = _capabilitiesProvider.GetCapabilities(Settings); parameters += string.Format("&tmdbid={0}", searchCriteria.TmdbId.Value);
return capabilities.MovieSearchParams != null &&
capabilities.MovieSearchParams.Contains(MovieSearchParam.ImdbId);
}
} }
private bool SupportsTmdbSearch if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace() && capabilities.MovieSearchImdbAvailable)
{
get
{ {
var capabilities = _capabilitiesProvider.GetCapabilities(Settings); parameters += string.Format("&imdbid={0}", searchCriteria.ImdbId);
return capabilities.MovieSearchParams != null &&
capabilities.MovieSearchParams.Contains(MovieSearchParam.TmdbId);
}
} }
private bool SupportsAggregatedIdSearch if (searchCriteria.TraktId.HasValue && capabilities.MovieSearchTraktAvailable)
{
get
{ {
var capabilities = _capabilitiesProvider.GetCapabilities(Settings); parameters += string.Format("&traktid={0}", searchCriteria.ImdbId);
// TODO: Fix this, return capabilities.SupportsAggregateIdSearch;
return true;
}
} }
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria) if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
{ {
var pageableRequests = new IndexerPageableRequestChain(); parameters += string.Format("&q={0}", searchCriteria.SearchTerm);
}
AddMovieIdPageableRequests(pageableRequests, MaxPages, searchCriteria.Categories, searchCriteria); pageableRequests.Add(GetPagedRequests(searchCriteria,
parameters));
return pageableRequests; return pageableRequests;
} }
@ -82,86 +62,73 @@ namespace NzbDrone.Core.Indexers.Newznab
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria) public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{ {
return new IndexerPageableRequestChain(); var capabilities = _capabilitiesProvider.GetCapabilities(Settings);
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria) var pageableRequests = new IndexerPageableRequestChain();
var parameters = string.Empty;
if (searchCriteria.TvdbId.HasValue && capabilities.TvSearchTvdbAvailable)
{ {
return new IndexerPageableRequestChain(); parameters += string.Format("&tvdbid={0}", searchCriteria.TvdbId.Value);
} }
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria) if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace() && capabilities.TvSearchImdbAvailable)
{ {
var pageableRequests = new IndexerPageableRequestChain(); parameters += string.Format("&imdbid={0}", searchCriteria.ImdbId);
pageableRequests.Add(GetPagedRequests(MaxPages,
searchCriteria.Categories,
"search",
string.Format("&q={0}", NewsnabifyTitle(searchCriteria.SearchTerm))));
return pageableRequests;
} }
private void AddMovieIdPageableRequests(IndexerPageableRequestChain chain, int maxPages, IEnumerable<int> categories, MovieSearchCriteria searchCriteria) if (searchCriteria.TvMazeId.HasValue && capabilities.TvSearchTvMazeAvailable)
{ {
var includeTmdbSearch = SupportsTmdbSearch && searchCriteria.TmdbId > 0; parameters += string.Format("&tvmazeid={0}", searchCriteria.TvMazeId);
var includeImdbSearch = SupportsImdbSearch && searchCriteria.ImdbId.IsNotNullOrWhiteSpace(); }
if (SupportsAggregatedIdSearch && (includeTmdbSearch || includeImdbSearch)) if (searchCriteria.RId.HasValue && capabilities.TvSearchTvRageAvailable)
{ {
var ids = ""; parameters += string.Format("&rid={0}", searchCriteria.RId);
}
if (includeTmdbSearch) if (searchCriteria.Season.HasValue && capabilities.TvSearchSeasonAvailable)
{ {
ids += "&tmdbid=" + searchCriteria.TmdbId; parameters += string.Format("&season={0}", searchCriteria.Season);
} }
if (includeImdbSearch) if (searchCriteria.Ep.HasValue && capabilities.TvSearchEpAvailable)
{ {
ids += "&imdbid=" + searchCriteria.ImdbId.Substring(2); parameters += string.Format("&ep={0}", searchCriteria.Ep);
} }
chain.Add(GetPagedRequests(maxPages, categories, "movie", ids)); if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
}
else
{ {
if (includeTmdbSearch) parameters += string.Format("&q={0}", searchCriteria.SearchTerm);
{
chain.Add(GetPagedRequests(maxPages,
categories,
"movie",
string.Format("&tmdbid={0}", searchCriteria.TmdbId)));
} }
else if (includeImdbSearch)
{ pageableRequests.Add(GetPagedRequests(searchCriteria,
chain.Add(GetPagedRequests(maxPages, parameters));
categories,
"movie", return pageableRequests;
string.Format("&imdbid={0}", searchCriteria.ImdbId.Substring(2))));
} }
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
} }
if (SupportsSearch) public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{ {
chain.AddTier(); var pageableRequests = new IndexerPageableRequestChain();
var searchQuery = searchCriteria.SearchTerm; var searchQuery = searchCriteria.SearchTerm;
if (!Settings.RemoveYear) pageableRequests.Add(GetPagedRequests(searchCriteria,
{ searchQuery.IsNotNullOrWhiteSpace() ? string.Format("&q={0}", NewsnabifyTitle(searchCriteria.SearchTerm)) : string.Empty));
searchQuery = string.Format("{0}", searchQuery);
}
chain.Add(GetPagedRequests(MaxPages, return pageableRequests;
categories,
"movie",
string.Format("&q={0}", NewsnabifyTitle(searchQuery))));
}
} }
private IEnumerable<IndexerRequest> GetPagedRequests(int maxPages, IEnumerable<int> categories, string searchType, string parameters) private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCriteria, string parameters)
{ {
var baseUrl = string.Format("{0}{1}?t={2}&extended=1", Settings.BaseUrl.TrimEnd('/'), Settings.ApiPath.TrimEnd('/'), searchType); var baseUrl = string.Format("{0}{1}?t={2}&extended=1", Settings.BaseUrl.TrimEnd('/'), Settings.ApiPath.TrimEnd('/'), searchCriteria.SearchType);
var categories = searchCriteria.Categories;
if (categories != null && categories.Any()) if (categories != null && categories.Any())
{ {
@ -174,16 +141,23 @@ namespace NzbDrone.Core.Indexers.Newznab
baseUrl += "&apikey=" + Settings.ApiKey; baseUrl += "&apikey=" + Settings.ApiKey;
} }
if (searchCriteria.Limit.HasValue)
{
parameters += string.Format("&limit={0}", searchCriteria.Limit);
}
if (searchCriteria.Offset.HasValue)
{
parameters += string.Format("&offset={0}", searchCriteria.Offset);
}
if (PageSize == 0) if (PageSize == 0)
{ {
yield return new IndexerRequest(string.Format("{0}{1}", baseUrl, parameters), HttpAccept.Rss); yield return new IndexerRequest(string.Format("{0}{1}", baseUrl, parameters), HttpAccept.Rss);
} }
else else
{ {
for (var page = 0; page < maxPages; page++) yield return new IndexerRequest(string.Format("{0}&offset={1}&limit={2}{3}", baseUrl, searchCriteria.Offset, searchCriteria.Limit, parameters), HttpAccept.Rss);
{
yield return new IndexerRequest(string.Format("{0}&offset={1}&limit={2}{3}", baseUrl, page * PageSize, PageSize, parameters), HttpAccept.Rss);
}
} }
} }

@ -96,6 +96,27 @@ namespace NzbDrone.Core.Indexers.Newznab
return ParseUrl(item.TryGetValue("comments")); return ParseUrl(item.TryGetValue("comments"));
} }
protected override ICollection<IndexerCategory> GetCategory(XElement item)
{
var cats = TryGetMultipleNewznabAttributes(item, "category");
var results = new List<IndexerCategory>();
foreach (var cat in cats)
{
if (int.TryParse(cat, out var intCategory))
{
var indexerCat = _settings.Categories.FirstOrDefault(c => c.Id == intCategory);
if (indexerCat != null)
{
results.Add(indexerCat);
}
}
}
return results;
}
protected override long GetSize(XElement item) protected override long GetSize(XElement item)
{ {
long size; long size;
@ -174,5 +195,22 @@ namespace NzbDrone.Core.Indexers.Newznab
return defaultValue; return defaultValue;
} }
protected List<string> TryGetMultipleNewznabAttributes(XElement item, string key)
{
var attrElements = item.Elements(ns + "attr").Where(e => e.Attribute("name").Value.Equals(key, StringComparison.OrdinalIgnoreCase));
var results = new List<string>();
foreach (var element in attrElements)
{
var attrValue = element.Attribute("value");
if (attrValue != null)
{
results.Add(attrValue.Value);
}
}
return results;
}
} }
} }

@ -76,6 +76,8 @@ namespace NzbDrone.Core.Indexers.Newznab
Type = FieldType.Checkbox)] Type = FieldType.Checkbox)]
public bool RemoveYear { get; set; } public bool RemoveYear { get; set; }
public List<IndexerCategory> Categories { get; set; }
// Field 8 is used by TorznabSettings MinimumSeeders // Field 8 is used by TorznabSettings MinimumSeeders
// If you need to add another field here, update TorznabSettings as well and this comment // If you need to add another field here, update TorznabSettings as well and this comment
public virtual NzbDroneValidationResult Validate() public virtual NzbDroneValidationResult Validate()

@ -11,6 +11,7 @@ using NzbDrone.Core.Http.CloudFlare;
using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers namespace NzbDrone.Core.Indexers
{ {
@ -292,6 +293,11 @@ namespace NzbDrone.Core.Indexers
return CleanupReleases(releases); return CleanupReleases(releases);
} }
public override IndexerCapabilities GetCapabilities()
{
return Capabilities ?? ((IndexerDefinition)Definition).Capabilities;
}
protected virtual bool IsValidRelease(ReleaseInfo release) protected virtual bool IsValidRelease(ReleaseInfo release)
{ {
if (release.DownloadUrl == null) if (release.DownloadUrl == null)

@ -19,5 +19,7 @@ namespace NzbDrone.Core.Indexers
IList<ReleaseInfo> Fetch(TvSearchCriteria searchCriteria); IList<ReleaseInfo> Fetch(TvSearchCriteria searchCriteria);
IList<ReleaseInfo> Fetch(BookSearchCriteria searchCriteria); IList<ReleaseInfo> Fetch(BookSearchCriteria searchCriteria);
IList<ReleaseInfo> Fetch(BasicSearchCriteria searchCriteria); IList<ReleaseInfo> Fetch(BasicSearchCriteria searchCriteria);
IndexerCapabilities GetCapabilities();
} }
} }

@ -71,6 +71,8 @@ namespace NzbDrone.Core.Indexers
public abstract IList<ReleaseInfo> Fetch(BookSearchCriteria searchCriteria); public abstract IList<ReleaseInfo> Fetch(BookSearchCriteria searchCriteria);
public abstract IList<ReleaseInfo> Fetch(BasicSearchCriteria searchCriteria); public abstract IList<ReleaseInfo> Fetch(BasicSearchCriteria searchCriteria);
public abstract IndexerCapabilities GetCapabilities();
protected virtual IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> releases) protected virtual IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> releases)
{ {
var result = releases.DistinctBy(v => v.Guid).ToList(); var result = releases.DistinctBy(v => v.Guid).ToList();

@ -22,7 +22,8 @@ namespace NzbDrone.Core.Indexers
ImdbId, ImdbId,
TmdbId, TmdbId,
ImdbTitle, ImdbTitle,
ImdbYear ImdbYear,
TraktId,
} }
public enum MusicSearchParam public enum MusicSearchParam
@ -67,6 +68,7 @@ namespace NzbDrone.Core.Indexers
public bool MovieSearchAvailable => MovieSearchParams.Count > 0; public bool MovieSearchAvailable => MovieSearchParams.Count > 0;
public bool MovieSearchImdbAvailable => MovieSearchParams.Contains(MovieSearchParam.ImdbId); public bool MovieSearchImdbAvailable => MovieSearchParams.Contains(MovieSearchParam.ImdbId);
public bool MovieSearchTmdbAvailable => MovieSearchParams.Contains(MovieSearchParam.TmdbId); public bool MovieSearchTmdbAvailable => MovieSearchParams.Contains(MovieSearchParam.TmdbId);
public bool MovieSearchTraktAvailable => MovieSearchParams.Contains(MovieSearchParam.TraktId);
public List<MusicSearchParam> MusicSearchParams; public List<MusicSearchParam> MusicSearchParams;
public bool MusicSearchAvailable => MusicSearchParams.Count > 0; public bool MusicSearchAvailable => MusicSearchParams.Count > 0;

@ -168,7 +168,7 @@ namespace NzbDrone.Core.Indexers
private void AddTorznabCategoryTree(IndexerCategory torznabCategory) private void AddTorznabCategoryTree(IndexerCategory torznabCategory)
{ {
// build the category tree // build the category tree
if (TorznabCatType.ParentCats.Contains(torznabCategory)) if (NewznabStandardCategory.ParentCats.Contains(torznabCategory))
{ {
// parent cat // parent cat
if (!_torznabCategoryTree.Contains(torznabCategory)) if (!_torznabCategoryTree.Contains(torznabCategory))
@ -179,7 +179,7 @@ namespace NzbDrone.Core.Indexers
else else
{ {
// child or custom cat // child or custom cat
var parentCat = TorznabCatType.ParentCats.FirstOrDefault(c => c.Contains(torznabCategory)); var parentCat = NewznabStandardCategory.ParentCats.FirstOrDefault(c => c.Contains(torznabCategory));
if (parentCat != null) if (parentCat != null)
{ {
// child cat // child cat
@ -203,9 +203,39 @@ namespace NzbDrone.Core.Indexers
else else
{ {
// custom cat // custom cat
if (torznabCategory.Id > 1000 && torznabCategory.Id < 10000)
{
var potentialParent = NewznabStandardCategory.ParentCats.FirstOrDefault(c => (c.Id / 1000) == (torznabCategory.Id / 1000));
if (potentialParent != null)
{
var nodeCat = _torznabCategoryTree.FirstOrDefault(c => c.Equals(potentialParent));
if (nodeCat != null)
{
// parent cat already exists
if (!nodeCat.Contains(torznabCategory))
{
nodeCat.SubCategories.Add(torznabCategory);
}
}
else
{
// create parent cat and add child
nodeCat = potentialParent.CopyWithoutSubCategories();
nodeCat.SubCategories.Add(torznabCategory);
_torznabCategoryTree.Add(nodeCat);
}
}
else
{
_torznabCategoryTree.Add(torznabCategory); _torznabCategoryTree.Add(torznabCategory);
} }
} }
else
{
_torznabCategoryTree.Add(torznabCategory);
}
}
}
} }
} }
} }

@ -5,6 +5,7 @@ using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Composition; using NzbDrone.Common.Composition;
using NzbDrone.Core.Indexers.Cardigann; using NzbDrone.Core.Indexers.Cardigann;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.IndexerVersions; using NzbDrone.Core.IndexerVersions;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
@ -22,10 +23,12 @@ namespace NzbDrone.Core.Indexers
public class IndexerFactory : ProviderFactory<IIndexer, IndexerDefinition>, IIndexerFactory public class IndexerFactory : ProviderFactory<IIndexer, IndexerDefinition>, IIndexerFactory
{ {
private readonly IIndexerDefinitionUpdateService _definitionService; private readonly IIndexerDefinitionUpdateService _definitionService;
private readonly INewznabCapabilitiesProvider _newznabCapabilitiesProvider;
private readonly IIndexerStatusService _indexerStatusService; private readonly IIndexerStatusService _indexerStatusService;
private readonly Logger _logger; private readonly Logger _logger;
public IndexerFactory(IIndexerDefinitionUpdateService definitionService, public IndexerFactory(IIndexerDefinitionUpdateService definitionService,
INewznabCapabilitiesProvider newznabCapabilitiesProvider,
IIndexerStatusService indexerStatusService, IIndexerStatusService indexerStatusService,
IIndexerRepository providerRepository, IIndexerRepository providerRepository,
IEnumerable<IIndexer> providers, IEnumerable<IIndexer> providers,
@ -36,6 +39,7 @@ namespace NzbDrone.Core.Indexers
{ {
_definitionService = definitionService; _definitionService = definitionService;
_indexerStatusService = indexerStatusService; _indexerStatusService = indexerStatusService;
_newznabCapabilitiesProvider = newznabCapabilitiesProvider;
_logger = logger; _logger = logger;
} }
@ -89,7 +93,7 @@ namespace NzbDrone.Core.Indexers
{ {
foreach (var category in defFile.Caps.Categories) foreach (var category in defFile.Caps.Categories)
{ {
var cat = TorznabCatType.GetCatByName(category.Value); var cat = NewznabStandardCategory.GetCatByName(category.Value);
if (cat == null) if (cat == null)
{ {
@ -108,7 +112,7 @@ namespace NzbDrone.Core.Indexers
if (categorymapping.cat != null) if (categorymapping.cat != null)
{ {
torznabCat = TorznabCatType.GetCatByName(categorymapping.cat); torznabCat = NewznabStandardCategory.GetCatByName(categorymapping.cat);
if (torznabCat == null) if (torznabCat == null)
{ {
continue; continue;
@ -242,6 +246,14 @@ namespace NzbDrone.Core.Indexers
{ {
definition.Added = DateTime.UtcNow; definition.Added = DateTime.UtcNow;
var provider = _providers.First(v => v.GetType().Name == definition.Implementation);
if (definition.Implementation == typeof(Newznab.Newznab).Name)
{
var settings = (NewznabSettings)definition.Settings;
settings.Categories = _newznabCapabilitiesProvider.GetCapabilities(settings).Categories.GetTorznabCategoryList();
}
return base.Create(definition); return base.Create(definition);
} }
} }

@ -3,7 +3,7 @@ using System.Linq;
namespace NzbDrone.Core.Indexers namespace NzbDrone.Core.Indexers
{ {
public static class TorznabCatType public static class NewznabStandardCategory
{ {
public static readonly IndexerCategory Console = new IndexerCategory(1000, "Console"); public static readonly IndexerCategory Console = new IndexerCategory(1000, "Console");
public static readonly IndexerCategory ConsoleNDS = new IndexerCategory(1010, "Console/NDS"); public static readonly IndexerCategory ConsoleNDS = new IndexerCategory(1010, "Console/NDS");
@ -171,7 +171,7 @@ namespace NzbDrone.Core.Indexers
OtherHashed OtherHashed
}; };
static TorznabCatType() static NewznabStandardCategory()
{ {
Console.SubCategories.AddRange( Console.SubCategories.AddRange(
new List<IndexerCategory> new List<IndexerCategory>

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -160,6 +160,7 @@ namespace NzbDrone.Core.Indexers
releaseInfo.DownloadUrl = GetDownloadUrl(item); releaseInfo.DownloadUrl = GetDownloadUrl(item);
releaseInfo.InfoUrl = GetInfoUrl(item); releaseInfo.InfoUrl = GetInfoUrl(item);
releaseInfo.CommentUrl = GetCommentUrl(item); releaseInfo.CommentUrl = GetCommentUrl(item);
releaseInfo.Category = GetCategory(item);
try try
{ {
@ -188,6 +189,11 @@ namespace NzbDrone.Core.Indexers
return item.TryGetValue("title", "Unknown"); return item.TryGetValue("title", "Unknown");
} }
protected virtual ICollection<IndexerCategory> GetCategory(XElement item)
{
return new List<IndexerCategory> { NewznabStandardCategory.Other };
}
protected virtual DateTime GetPublishDate(XElement item) protected virtual DateTime GetPublishDate(XElement item)
{ {
var dateString = item.TryGetValue("pubDate"); var dateString = item.TryGetValue("pubDate");

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.Applications;
using NzbDrone.Core.Backup; using NzbDrone.Core.Backup;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.HealthCheck; using NzbDrone.Core.HealthCheck;
@ -61,6 +62,7 @@ namespace NzbDrone.Core.Jobs
new ScheduledTask { Interval = 6 * 60, TypeName = typeof(CheckHealthCommand).FullName }, new ScheduledTask { Interval = 6 * 60, TypeName = typeof(CheckHealthCommand).FullName },
new ScheduledTask { Interval = 24 * 60, TypeName = typeof(HousekeepingCommand).FullName }, new ScheduledTask { Interval = 24 * 60, TypeName = typeof(HousekeepingCommand).FullName },
new ScheduledTask { Interval = 6 * 60, TypeName = typeof(IndexerDefinitionUpdateCommand).FullName }, new ScheduledTask { Interval = 6 * 60, TypeName = typeof(IndexerDefinitionUpdateCommand).FullName },
new ScheduledTask { Interval = 6 * 60, TypeName = typeof(ApplicationIndexerSyncCommand).FullName },
new ScheduledTask new ScheduledTask
{ {

@ -14,8 +14,8 @@
"AddMovies": "Add Movies", "AddMovies": "Add Movies",
"AddMoviesMonitored": "Add Movies Monitored", "AddMoviesMonitored": "Add Movies Monitored",
"AddNew": "Add New", "AddNew": "Add New",
"AddNewMessage": "It's easy to add a new movie, just start typing the name of the movie you want to add",
"AddNewIndexer": "Add New Indexer", "AddNewIndexer": "Add New Indexer",
"AddNewMessage": "It's easy to add a new movie, just start typing the name of the movie you want to add",
"AddNewTmdbIdMessage": "You can also search using TMDb Id of a movie. eg. tmdb:71663", "AddNewTmdbIdMessage": "You can also search using TMDb Id of a movie. eg. tmdb:71663",
"AddRemotePathMapping": "Add Remote Path Mapping", "AddRemotePathMapping": "Add Remote Path Mapping",
"AddRestriction": "Add Restriction", "AddRestriction": "Add Restriction",
@ -332,6 +332,8 @@
"IncludeUnmonitored": "Include Unmonitored", "IncludeUnmonitored": "Include Unmonitored",
"Indexer": "Indexer", "Indexer": "Indexer",
"IndexerFlags": "Indexer Flags", "IndexerFlags": "Indexer Flags",
"IndexerLongTermStatusCheckAllClientMessage": "All indexers are unavailable due to failures for more than 6 hours",
"IndexerLongTermStatusCheckSingleClientMessage": "Indexers unavailable due to failures for more than 6 hours: {0}",
"IndexerPriority": "Indexer Priority", "IndexerPriority": "Indexer Priority",
"IndexerPriorityHelpText": "Indexer Priority from 1 (Highest) to 50 (Lowest). Default: 25.", "IndexerPriorityHelpText": "Indexer Priority from 1 (Highest) to 50 (Lowest). Default: 25.",
"IndexerRssHealthCheckNoAvailableIndexers": "All rss-capable indexers are temporarily unavailable due to recent indexer errors", "IndexerRssHealthCheckNoAvailableIndexers": "All rss-capable indexers are temporarily unavailable due to recent indexer errors",
@ -341,11 +343,10 @@
"IndexerSearchCheckNoAvailableIndexersMessage": "All search-capable indexers are temporarily unavailable due to recent indexer errors", "IndexerSearchCheckNoAvailableIndexersMessage": "All search-capable indexers are temporarily unavailable due to recent indexer errors",
"IndexerSearchCheckNoInteractiveMessage": "No indexers available with Interactive Search enabled, Prowlarr will not provide any interactive search results", "IndexerSearchCheckNoInteractiveMessage": "No indexers available with Interactive Search enabled, Prowlarr will not provide any interactive search results",
"IndexerSettings": "Indexer Settings", "IndexerSettings": "Indexer Settings",
"IndexersSelectedInterp": "{0} Indexer(s) Selected",
"IndexersSettingsSummary": "Indexers and release restrictions", "IndexersSettingsSummary": "Indexers and release restrictions",
"IndexerStatusCheckAllClientMessage": "All indexers are unavailable due to failures", "IndexerStatusCheckAllClientMessage": "All indexers are unavailable due to failures",
"IndexerStatusCheckSingleClientMessage": "Indexers unavailable due to failures: {0}", "IndexerStatusCheckSingleClientMessage": "Indexers unavailable due to failures: {0}",
"IndexerLongTermStatusCheckAllClientMessage": "All indexers are unavailable due to failures for more than 6 hours",
"IndexerLongTermStatusCheckSingleClientMessage": "Indexers unavailable due to failures for more than 6 hours: {0}",
"Info": "Info", "Info": "Info",
"InteractiveImport": "Interactive Import", "InteractiveImport": "Interactive Import",
"InteractiveSearch": "Interactive Search", "InteractiveSearch": "Interactive Search",
@ -452,7 +453,6 @@
"MovieIsUnmonitored": "Movie is unmonitored", "MovieIsUnmonitored": "Movie is unmonitored",
"MovieNaming": "Movie Naming", "MovieNaming": "Movie Naming",
"Movies": "Movies", "Movies": "Movies",
"IndexersSelectedInterp": "{0} Indexer(s) Selected",
"MovieTitle": "Movie Title", "MovieTitle": "Movie Title",
"MovieTitleHelpText": "The title of the movie to exclude (can be anything meaningful)", "MovieTitleHelpText": "The title of the movie to exclude (can be anything meaningful)",
"MovieYear": "Movie Year", "MovieYear": "Movie Year",
@ -530,6 +530,12 @@
"Proper": "Proper", "Proper": "Proper",
"Protocol": "Protocol", "Protocol": "Protocol",
"ProtocolHelpText": "Choose which protocol(s) to use and which one is preferred when choosing between otherwise equal releases", "ProtocolHelpText": "Choose which protocol(s) to use and which one is preferred when choosing between otherwise equal releases",
"Prowlarr": "Prowlarr",
"ProwlarrSupportsAnyDownloadClient": "Prowlarr supports any download client that uses the Newznab standard, as well as other download clients listed below.",
"ProwlarrSupportsAnyIndexer": "Prowlarr supports any indexer that uses the Newznab standard, as well as other indexers listed below.",
"ProwlarrSupportsAnyRSSMovieListsAsWellAsTheOneStatedBelow": "Prowlarr supports any RSS movie lists as well as the one stated below.",
"ProwlarrSupportsCustomConditionsAgainstTheReleasePropertiesBelow": "Prowlarr supports custom conditions against the release properties below.",
"ProwlarrTags": "Prowlarr Tags",
"Proxy": "Proxy", "Proxy": "Proxy",
"ProxyBypassFilterHelpText": "Use ',' as a separator, and '*.' as a wildcard for subdomains", "ProxyBypassFilterHelpText": "Use ',' as a separator, and '*.' as a wildcard for subdomains",
"ProxyCheckBadRequestMessage": "Failed to test proxy. StatusCode: {0}", "ProxyCheckBadRequestMessage": "Failed to test proxy. StatusCode: {0}",
@ -551,12 +557,6 @@
"Queue": "Queue", "Queue": "Queue",
"Queued": "Queued", "Queued": "Queued",
"QuickImport": "Quick Import", "QuickImport": "Quick Import",
"Prowlarr": "Prowlarr",
"ProwlarrSupportsAnyDownloadClient": "Prowlarr supports any download client that uses the Newznab standard, as well as other download clients listed below.",
"ProwlarrSupportsAnyIndexer": "Prowlarr supports any indexer that uses the Newznab standard, as well as other indexers listed below.",
"ProwlarrSupportsAnyRSSMovieListsAsWellAsTheOneStatedBelow": "Prowlarr supports any RSS movie lists as well as the one stated below.",
"ProwlarrSupportsCustomConditionsAgainstTheReleasePropertiesBelow": "Prowlarr supports custom conditions against the release properties below.",
"ProwlarrTags": "Prowlarr Tags",
"Ratings": "Ratings", "Ratings": "Ratings",
"ReadTheWikiForMoreInformation": "Read the Wiki for more information", "ReadTheWikiForMoreInformation": "Read the Wiki for more information",
"Real": "Real", "Real": "Real",
@ -727,6 +727,7 @@
"Style": "Style", "Style": "Style",
"SubfolderWillBeCreatedAutomaticallyInterp": "'{0}' subfolder will be created automatically", "SubfolderWillBeCreatedAutomaticallyInterp": "'{0}' subfolder will be created automatically",
"SuggestTranslationChange": "Suggest translation change", "SuggestTranslationChange": "Suggest translation change",
"SyncAppIndexers": "Sync App Indexers",
"System": "System", "System": "System",
"SystemTimeCheckMessage": "System time is off by more than 1 day. Scheduled tasks may not run correctly until the time is corrected", "SystemTimeCheckMessage": "System time is off by more than 1 day. Scheduled tasks may not run correctly until the time is corrected",
"Table": "Table", "Table": "Table",
@ -740,6 +741,7 @@
"Tasks": "Tasks", "Tasks": "Tasks",
"Test": "Test", "Test": "Test",
"TestAll": "Test All", "TestAll": "Test All",
"TestAllApps": "Test All Apps",
"TestAllClients": "Test All Clients", "TestAllClients": "Test All Clients",
"TestAllIndexers": "Test All Indexers", "TestAllIndexers": "Test All Indexers",
"TestAllLists": "Test All Lists", "TestAllLists": "Test All Lists",

@ -59,7 +59,7 @@ namespace Prowlarr.Api.V1.Indexers
switch (requestType) switch (requestType)
{ {
case "caps": case "caps":
Response response = indexer.Capabilities.ToXml(); Response response = indexerInstance.GetCapabilities().ToXml();
response.ContentType = "application/rss+xml"; response.ContentType = "application/rss+xml";
return response; return response;
case "tvsearch": case "tvsearch":

Loading…
Cancel
Save