mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-04-16 21:35:04 -04:00
Compare commits
17 Commits
cardigann-
...
v0.4.11.21
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57dcd861a9 | ||
|
|
dfe132cda2 | ||
|
|
a635820b48 | ||
|
|
d959e81efb | ||
|
|
ac89cd636f | ||
|
|
50616f5c9e | ||
|
|
3f9cb2c6ea | ||
|
|
b5aa85a548 | ||
|
|
0fa5127c83 | ||
|
|
4d137886bc | ||
|
|
9dde041c99 | ||
|
|
a8234c9ce0 | ||
|
|
9227efdb65 | ||
|
|
fa923e658f | ||
|
|
364a5564ae | ||
|
|
9efd0b391e | ||
|
|
320161e051 |
@@ -123,7 +123,7 @@ class AddIndexerModalContent extends Component {
|
||||
const filteredIndexers = indexers.filter((indexer) => {
|
||||
const { filter, filterProtocols, filterLanguages, filterPrivacyLevels } = this.state;
|
||||
|
||||
if (!indexer.name.toLowerCase().includes(filter.toLocaleLowerCase())) {
|
||||
if (!indexer.name.toLowerCase().includes(filter.toLocaleLowerCase()) && !indexer.description.toLowerCase().includes(filter.toLocaleLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -272,6 +272,7 @@ class IndexerIndex extends Component {
|
||||
saveError,
|
||||
isDeleting,
|
||||
isTestingAll,
|
||||
isSyncingIndexers,
|
||||
deleteError,
|
||||
onScroll,
|
||||
onSortSelect,
|
||||
@@ -309,6 +310,15 @@ class IndexerIndex extends Component {
|
||||
onPress={this.onAddIndexerPress}
|
||||
/>
|
||||
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('SyncAppIndexers')}
|
||||
iconName={icons.REFRESH}
|
||||
isSpinning={isSyncingIndexers}
|
||||
onPress={this.props.onAppIndexerSyncPress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('TestAllIndexers')}
|
||||
iconName={icons.TEST}
|
||||
@@ -493,10 +503,12 @@ IndexerIndex.propTypes = {
|
||||
saveError: PropTypes.object,
|
||||
isDeleting: PropTypes.bool.isRequired,
|
||||
isTestingAll: PropTypes.bool.isRequired,
|
||||
isSyncingIndexers: PropTypes.bool.isRequired,
|
||||
deleteError: PropTypes.object,
|
||||
onSortSelect: PropTypes.func.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
onTestAllPress: PropTypes.func.isRequired,
|
||||
onAppIndexerSyncPress: PropTypes.func.isRequired,
|
||||
onScroll: PropTypes.func.isRequired,
|
||||
onSaveSelected: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -2,10 +2,13 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import withScrollPosition from 'Components/withScrollPosition';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { testAllIndexers } from 'Store/Actions/indexerActions';
|
||||
import { saveIndexerEditor, setMovieFilter, setMovieSort, setMovieTableOption } from 'Store/Actions/indexerIndexActions';
|
||||
import scrollPositions from 'Store/scrollPositions';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createIndexerClientSideCollectionItemsSelector from 'Store/Selectors/createIndexerClientSideCollectionItemsSelector';
|
||||
import IndexerIndex from './IndexerIndex';
|
||||
@@ -13,13 +16,16 @@ import IndexerIndex from './IndexerIndex';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createIndexerClientSideCollectionItemsSelector('indexerIndex'),
|
||||
createCommandExecutingSelector(commandNames.APP_INDEXER_SYNC),
|
||||
createDimensionsSelector(),
|
||||
(
|
||||
indexers,
|
||||
isSyncingIndexers,
|
||||
dimensionsState
|
||||
) => {
|
||||
return {
|
||||
...indexers,
|
||||
isSyncingIndexers,
|
||||
isSmallScreen: dimensionsState.isSmallScreen
|
||||
};
|
||||
}
|
||||
@@ -46,6 +52,12 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
|
||||
onTestAllPress() {
|
||||
dispatch(testAllIndexers());
|
||||
},
|
||||
|
||||
onAppIndexerSyncPress() {
|
||||
dispatch(executeCommand({
|
||||
name: commandNames.APP_INDEXER_SYNC
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -81,8 +81,6 @@ class EditDownloadClientModalContent extends Component {
|
||||
message
|
||||
} = item;
|
||||
|
||||
console.log(supportsCategories);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
|
||||
@@ -14,8 +14,6 @@ function createLanguagesSelector() {
|
||||
return createSelector(
|
||||
(state) => state.localization,
|
||||
(localization) => {
|
||||
console.log(localization);
|
||||
|
||||
const items = localization.items;
|
||||
|
||||
if (!items) {
|
||||
|
||||
@@ -113,8 +113,6 @@ export default {
|
||||
|
||||
const saveData = getProviderState({ id, ...otherPayload }, getState, section, false);
|
||||
|
||||
console.log(saveData);
|
||||
|
||||
// we have to set id since not actually posting to server yet
|
||||
if (!saveData.id) {
|
||||
saveData.id = getNextId(getState().settings.downloadClientCategories.items);
|
||||
|
||||
@@ -310,8 +310,6 @@ export const actionHandlers = handleThunks({
|
||||
isGrabbing: true
|
||||
}));
|
||||
|
||||
console.log(payload);
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/search/bulk',
|
||||
method: 'POST',
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<subcat id="5030" name="SD"/>
|
||||
<subcat id="5060" name="Sport"/>
|
||||
<subcat id="5010" name="WEB-DL"/>
|
||||
<subcat id="5999" name="Other"/>
|
||||
</category>
|
||||
<category id="7000" name="Other">
|
||||
<subcat id="7010" name="Misc"/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
@@ -70,6 +71,32 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
caps.LimitsMax.Value.Should().Be(60);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_map_different_categories()
|
||||
{
|
||||
GivenCapsResponse(_caps);
|
||||
|
||||
var caps = Subject.GetCapabilities(_settings, _definition);
|
||||
|
||||
var bookCats = caps.Categories.MapTorznabCapsToTrackers(new int[] { NewznabStandardCategory.Books.Id });
|
||||
|
||||
bookCats.Count.Should().Be(2);
|
||||
bookCats.Should().Contain("8000");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_map_by_name_when_available()
|
||||
{
|
||||
GivenCapsResponse(_caps);
|
||||
|
||||
var caps = Subject.GetCapabilities(_settings, _definition);
|
||||
|
||||
var bookCats = caps.Categories.MapTrackerCatToNewznab("5999");
|
||||
|
||||
bookCats.Count.Should().Be(2);
|
||||
bookCats.First().Id.Should().Be(5050);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_default_pagesize_if_missing()
|
||||
{
|
||||
|
||||
@@ -126,6 +126,12 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
{
|
||||
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
|
||||
{
|
||||
// Retain user fields not-affiliated with Prowlarr
|
||||
lidarrIndexer.Fields.AddRange(remoteIndexer.Fields.Where(f => !lidarrIndexer.Fields.Any(s => s.Name == f.Name)));
|
||||
|
||||
// Retain user settings not-affiliated with Prowlarr
|
||||
lidarrIndexer.DownloadClientId = remoteIndexer.DownloadClientId;
|
||||
|
||||
// Update the indexer if it still has categories that match
|
||||
_lidarrV1Proxy.UpdateIndexer(lidarrIndexer, Settings);
|
||||
}
|
||||
@@ -159,6 +165,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
{
|
||||
var cacheKey = $"{Settings.BaseUrl}";
|
||||
var schemas = _schemaCache.Get(cacheKey, () => _lidarrV1Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
||||
var syncFields = new string[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime" };
|
||||
|
||||
var newznab = schemas.Where(i => i.Implementation == "Newznab").First();
|
||||
var torznab = schemas.Where(i => i.Implementation == "Torznab").First();
|
||||
@@ -175,9 +182,11 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
Priority = indexer.Priority,
|
||||
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
|
||||
ConfigContract = schema.ConfigContract,
|
||||
Fields = schema.Fields,
|
||||
Fields = new List<LidarrField>()
|
||||
};
|
||||
|
||||
lidarrIndexer.Fields.AddRange(schema.Fields.Where(x => syncFields.Contains(x.Name)));
|
||||
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||
@@ -191,7 +200,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
|
||||
if (lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime") != null)
|
||||
{
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
|
||||
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
public string Implementation { get; set; }
|
||||
public string ConfigContract { get; set; }
|
||||
public string InfoLink { get; set; }
|
||||
public int? DownloadClientId { get; set; }
|
||||
public HashSet<int> Tags { get; set; }
|
||||
public List<LidarrField> Fields { get; set; }
|
||||
|
||||
@@ -33,7 +34,7 @@ namespace NzbDrone.Core.Applications.Lidarr
|
||||
|
||||
var apiPath = Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var otherApiPath = other.Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiPathCompare = apiPath == otherApiPath;
|
||||
var apiPathCompare = apiPath.Equals(otherApiPath);
|
||||
|
||||
var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
|
||||
@@ -126,6 +126,12 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
{
|
||||
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
|
||||
{
|
||||
// Retain user fields not-affiliated with Prowlarr
|
||||
radarrIndexer.Fields.AddRange(remoteIndexer.Fields.Where(f => !radarrIndexer.Fields.Any(s => s.Name == f.Name)));
|
||||
|
||||
// Retain user settings not-affiliated with Prowlarr
|
||||
radarrIndexer.DownloadClientId = remoteIndexer.DownloadClientId;
|
||||
|
||||
// Update the indexer if it still has categories that match
|
||||
_radarrV3Proxy.UpdateIndexer(radarrIndexer, Settings);
|
||||
}
|
||||
@@ -159,6 +165,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
{
|
||||
var cacheKey = $"{Settings.BaseUrl}";
|
||||
var schemas = _schemaCache.Get(cacheKey, () => _radarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
||||
var syncFields = new string[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime" };
|
||||
|
||||
var newznab = schemas.Where(i => i.Implementation == "Newznab").First();
|
||||
var torznab = schemas.Where(i => i.Implementation == "Torznab").First();
|
||||
@@ -175,9 +182,11 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
Priority = indexer.Priority,
|
||||
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
|
||||
ConfigContract = schema.ConfigContract,
|
||||
Fields = schema.Fields,
|
||||
Fields = new List<RadarrField>()
|
||||
};
|
||||
|
||||
radarrIndexer.Fields.AddRange(schema.Fields.Where(x => syncFields.Contains(x.Name)));
|
||||
|
||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
|
||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
public string Implementation { get; set; }
|
||||
public string ConfigContract { get; set; }
|
||||
public string InfoLink { get; set; }
|
||||
public int? DownloadClientId { get; set; }
|
||||
public HashSet<int> Tags { get; set; }
|
||||
public List<RadarrField> Fields { get; set; }
|
||||
|
||||
@@ -33,7 +34,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
||||
|
||||
var apiPath = Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var otherApiPath = other.Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiPathCompare = apiPath == otherApiPath;
|
||||
var apiPathCompare = apiPath.Equals(otherApiPath);
|
||||
|
||||
var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
|
||||
@@ -126,6 +126,8 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
{
|
||||
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
|
||||
{
|
||||
readarrIndexer.Fields.AddRange(remoteIndexer.Fields.Where(f => !readarrIndexer.Fields.Any(s => s.Name == f.Name)));
|
||||
|
||||
// Update the indexer if it still has categories that match
|
||||
_readarrV1Proxy.UpdateIndexer(readarrIndexer, Settings);
|
||||
}
|
||||
@@ -159,6 +161,7 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
{
|
||||
var cacheKey = $"{Settings.BaseUrl}";
|
||||
var schemas = _schemaCache.Get(cacheKey, () => _readarrV1Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
||||
var syncFields = new string[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime" };
|
||||
|
||||
var newznab = schemas.Where(i => i.Implementation == "Newznab").First();
|
||||
var torznab = schemas.Where(i => i.Implementation == "Torznab").First();
|
||||
@@ -175,9 +178,11 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
Priority = indexer.Priority,
|
||||
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
|
||||
ConfigContract = schema.ConfigContract,
|
||||
Fields = schema.Fields,
|
||||
Fields = new List<ReadarrField>()
|
||||
};
|
||||
|
||||
readarrIndexer.Fields.AddRange(schema.Fields.Where(x => syncFields.Contains(x.Name)));
|
||||
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||
@@ -191,7 +196,7 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
|
||||
if (readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime") != null)
|
||||
{
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
|
||||
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Readarr
|
||||
|
||||
var apiPath = Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var otherApiPath = other.Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiPathCompare = apiPath == otherApiPath;
|
||||
var apiPathCompare = apiPath.Equals(otherApiPath);
|
||||
|
||||
var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
|
||||
@@ -126,6 +126,13 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
{
|
||||
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any() || indexer.Capabilities.Categories.SupportedCategories(Settings.AnimeSyncCategories.ToArray()).Any())
|
||||
{
|
||||
// Retain user fields not-affiliated with Prowlarr
|
||||
sonarrIndexer.Fields.AddRange(remoteIndexer.Fields.Where(f => !sonarrIndexer.Fields.Any(s => s.Name == f.Name)));
|
||||
|
||||
// Retain user settings not-affiliated with Prowlarr
|
||||
sonarrIndexer.DownloadClientId = remoteIndexer.DownloadClientId;
|
||||
sonarrIndexer.SeasonSearchMaximumSingleEpisodeAge = remoteIndexer.SeasonSearchMaximumSingleEpisodeAge;
|
||||
|
||||
// Update the indexer if it still has categories that match
|
||||
_sonarrV3Proxy.UpdateIndexer(sonarrIndexer, Settings);
|
||||
}
|
||||
@@ -159,6 +166,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
{
|
||||
var cacheKey = $"{Settings.BaseUrl}";
|
||||
var schemas = _schemaCache.Get(cacheKey, () => _sonarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
||||
var syncFields = new string[] { "baseUrl", "apiPath", "apiKey", "categories", "animeCategories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime" };
|
||||
|
||||
var newznab = schemas.Where(i => i.Implementation == "Newznab").First();
|
||||
var torznab = schemas.Where(i => i.Implementation == "Torznab").First();
|
||||
@@ -175,9 +183,11 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
Priority = indexer.Priority,
|
||||
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
|
||||
ConfigContract = schema.ConfigContract,
|
||||
Fields = schema.Fields,
|
||||
Fields = new List<SonarrField>()
|
||||
};
|
||||
|
||||
sonarrIndexer.Fields.AddRange(schema.Fields.Where(x => syncFields.Contains(x.Name)));
|
||||
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||
@@ -189,7 +199,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders;
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
|
||||
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
|
||||
}
|
||||
|
||||
return sonarrIndexer;
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
public string Implementation { get; set; }
|
||||
public string ConfigContract { get; set; }
|
||||
public string InfoLink { get; set; }
|
||||
public int? DownloadClientId { get; set; }
|
||||
public int? SeasonSearchMaximumSingleEpisodeAge { get; set; }
|
||||
public HashSet<int> Tags { get; set; }
|
||||
public List<SonarrField> Fields { get; set; }
|
||||
|
||||
@@ -34,7 +36,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
||||
|
||||
var apiPath = Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var otherApiPath = other.Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiPathCompare = apiPath == otherApiPath;
|
||||
var apiPathCompare = apiPath.Equals(otherApiPath);
|
||||
|
||||
var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
|
||||
@@ -126,6 +126,8 @@ namespace NzbDrone.Core.Applications.Whisparr
|
||||
{
|
||||
if (indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray()).Any())
|
||||
{
|
||||
whisparrIndexer.Fields.AddRange(remoteIndexer.Fields.Where(f => !whisparrIndexer.Fields.Any(s => s.Name == f.Name)));
|
||||
|
||||
// Update the indexer if it still has categories that match
|
||||
_whisparrV3Proxy.UpdateIndexer(whisparrIndexer, Settings);
|
||||
}
|
||||
@@ -159,6 +161,7 @@ namespace NzbDrone.Core.Applications.Whisparr
|
||||
{
|
||||
var cacheKey = $"{Settings.BaseUrl}";
|
||||
var schemas = _schemaCache.Get(cacheKey, () => _whisparrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
|
||||
var syncFields = new string[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime" };
|
||||
|
||||
var newznab = schemas.Where(i => i.Implementation == "Newznab").First();
|
||||
var torznab = schemas.Where(i => i.Implementation == "Torznab").First();
|
||||
@@ -175,9 +178,11 @@ namespace NzbDrone.Core.Applications.Whisparr
|
||||
Priority = indexer.Priority,
|
||||
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
|
||||
ConfigContract = schema.ConfigContract,
|
||||
Fields = schema.Fields,
|
||||
Fields = new List<WhisparrField>()
|
||||
};
|
||||
|
||||
whisparrIndexer.Fields.AddRange(schema.Fields.Where(x => syncFields.Contains(x.Name)));
|
||||
|
||||
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl.TrimEnd('/')}/{indexer.Id}/";
|
||||
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "apiPath").Value = "/api";
|
||||
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey;
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Whisparr
|
||||
|
||||
var apiPath = Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var otherApiPath = other.Fields.FirstOrDefault(x => x.Name == "apiPath")?.Value == null ? null : other.Fields.FirstOrDefault(x => x.Name == "apiPath").Value;
|
||||
var apiPathCompare = apiPath == otherApiPath;
|
||||
var apiPathCompare = apiPath.Equals(otherApiPath);
|
||||
|
||||
var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value);
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
[CheckOn(typeof(ProviderAddedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderUpdatedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderBulkDeletedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderStatusChangedEvent<IIndexer>))]
|
||||
public class IndexerCheck : HealthCheckBase
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
[CheckOn(typeof(ProviderUpdatedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderBulkDeletedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderStatusChangedEvent<IIndexer>))]
|
||||
public class IndexerLongTermStatusCheck : HealthCheckBase
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
[CheckOn(typeof(ProviderUpdatedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderBulkDeletedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderStatusChangedEvent<IIndexer>))]
|
||||
public class IndexerStatusCheck : HealthCheckBase
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
[CheckOn(typeof(ProviderAddedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderUpdatedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderBulkDeletedEvent<IIndexer>))]
|
||||
public class IndexerVIPCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
[CheckOn(typeof(ProviderAddedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderUpdatedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderBulkDeletedEvent<IIndexer>))]
|
||||
public class IndexerVIPExpiredCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
|
||||
@@ -9,6 +9,7 @@ using NzbDrone.Core.ThingiProvider.Events;
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderBulkDeletedEvent<IIndexer>))]
|
||||
public class NoDefinitionCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IIndexerDefinitionUpdateService _indexerDefinitionUpdateService;
|
||||
|
||||
@@ -9,6 +9,7 @@ using NzbDrone.Core.ThingiProvider.Events;
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
[CheckOn(typeof(ProviderDeletedEvent<IIndexer>))]
|
||||
[CheckOn(typeof(ProviderBulkDeletedEvent<IIndexer>))]
|
||||
public class OutdatedDefinitionCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IIndexerDefinitionUpdateService _indexerDefinitionUpdateService;
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Cardigann
|
||||
{
|
||||
public class CardigannSettingsValidator : AbstractValidator<CardigannSettings>
|
||||
{
|
||||
public CardigannSettingsValidator()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class CardigannSettings : NoAuthTorrentBaseSettings
|
||||
{
|
||||
private static readonly CardigannSettingsValidator Validator = new CardigannSettingsValidator();
|
||||
|
||||
public CardigannSettings()
|
||||
{
|
||||
ExtraFieldData = new Dictionary<string, object>();
|
||||
@@ -26,10 +18,5 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
||||
public string DefinitionFile { get; set; }
|
||||
|
||||
public Dictionary<string, object> ExtraFieldData { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,11 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
|
||||
var baseUrl = string.Format("{0}/api.php?action={1}&category={2}&username={3}&passkey={4}{5}", Settings.BaseUrl.TrimEnd('/'), searchType, categoriesQuery, Settings.Username.Trim(), Settings.Passkey.Trim(), parameters);
|
||||
|
||||
if (Settings.FreeleechOnly)
|
||||
{
|
||||
baseUrl += "&freeleech=1";
|
||||
}
|
||||
|
||||
yield return new IndexerRequest(baseUrl, HttpAccept.Json);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
[FieldDefinition(3, Label = "Passkey", HelpText = "Site Passkey (This is the alphanumeric string in the tracker url shown in your download client)", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Passkey { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Freeleech Only", HelpText = "Search Freeleech torrents only", Type = FieldType.Checkbox)]
|
||||
public bool FreeleechOnly { get; set; }
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace NzbDrone.Core.Indexers.Headphones
|
||||
{
|
||||
RuleFor(c => c.Username).NotEmpty();
|
||||
RuleFor(c => c.Password).NotEmpty();
|
||||
RuleFor(x => x.BaseSettings).SetValidator(new IndexerCommonSettingsValidator());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Html.Parser;
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
@@ -25,7 +21,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public override string Name => "Nebulance";
|
||||
public override string[] IndexerUrls => new string[] { "https://nebulance.io/" };
|
||||
private string LoginUrl => Settings.BaseUrl + "login.php";
|
||||
public override string Description => "Nebulance (NBL) is a ratioless Private Torrent Tracker for TV";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
@@ -48,53 +43,13 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return new NebulanceParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override async Task DoLogin()
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(LoginUrl)
|
||||
{
|
||||
LogResponseContent = true
|
||||
};
|
||||
|
||||
requestBuilder.Method = HttpMethod.Post;
|
||||
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
var cookies = Cookies;
|
||||
|
||||
Cookies = null;
|
||||
var authLoginRequest = requestBuilder
|
||||
.AddFormParameter("username", Settings.Username)
|
||||
.AddFormParameter("password", Settings.Password)
|
||||
.AddFormParameter("twofa", Settings.TwoFactorAuth)
|
||||
.AddFormParameter("keeplogged", "on")
|
||||
.AddFormParameter("login", "Login")
|
||||
.SetHeader("Content-Type", "multipart/form-data")
|
||||
.Build();
|
||||
|
||||
var response = await ExecuteAuth(authLoginRequest);
|
||||
|
||||
cookies = response.GetCookies();
|
||||
UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30));
|
||||
|
||||
_logger.Debug("Nebulance authentication succeeded.");
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
if (!httpResponse.Content.Contains("logout.php"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
|
||||
}
|
||||
};
|
||||
|
||||
@@ -115,30 +70,16 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(NebulanceQuery parameters, int? results, int? offset)
|
||||
{
|
||||
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
|
||||
var apiUrl = Settings.BaseUrl + "api.php";
|
||||
|
||||
var searchTerm = term;
|
||||
var builder = new JsonRpcRequestBuilder(apiUrl)
|
||||
.Call("getTorrents", Settings.ApiKey, parameters, results ?? 100, offset ?? 0);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
searchTerm = Regex.Replace(searchTerm, @"[-._]", " ");
|
||||
}
|
||||
builder.SuppressHttpError = true;
|
||||
|
||||
var qc = new NameValueCollection
|
||||
{
|
||||
{ "action", "basic" },
|
||||
{ "order_by", "time" },
|
||||
{ "order_way", "desc" },
|
||||
{ "searchtext", searchTerm }
|
||||
};
|
||||
|
||||
searchUrl = searchUrl + "?" + qc.GetQueryString();
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
|
||||
yield return request;
|
||||
yield return new IndexerRequest(builder.Build());
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
@@ -159,7 +100,27 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
|
||||
var queryParams = new NebulanceQuery
|
||||
{
|
||||
Age = ">0"
|
||||
};
|
||||
|
||||
if (searchCriteria.SanitizedTvSearchString.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
queryParams.Name = "%" + searchCriteria.SanitizedTvSearchString + "%";
|
||||
}
|
||||
|
||||
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace() && int.TryParse(searchCriteria.ImdbId, out var intImdb))
|
||||
{
|
||||
queryParams.Imdb = intImdb;
|
||||
|
||||
if (searchCriteria.EpisodeSearchString.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
queryParams.Name = "%" + searchCriteria.EpisodeSearchString + "%";
|
||||
}
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(queryParams, searchCriteria.Limit, searchCriteria.Offset));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -175,7 +136,17 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
|
||||
var queryParams = new NebulanceQuery
|
||||
{
|
||||
Age = ">0"
|
||||
};
|
||||
|
||||
if (searchCriteria.SanitizedSearchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
queryParams.Name = "%" + searchCriteria.SanitizedSearchTerm + "%";
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(queryParams, searchCriteria.Limit, searchCriteria.Offset));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
@@ -199,60 +170,38 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var document = parser.ParseDocument(indexerResponse.Content);
|
||||
var rows = document.QuerySelectorAll(".torrent_table > tbody > tr[class^='torrent row']");
|
||||
JsonRpcResponse<NebulanceTorrents> jsonResponse = new HttpResponse<JsonRpcResponse<NebulanceTorrents>>(indexerResponse.HttpResponse).Resource;
|
||||
|
||||
if (jsonResponse.Error != null || jsonResponse.Result == null)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, "Indexer API call returned an error [{0}]", jsonResponse.Error);
|
||||
}
|
||||
|
||||
if (jsonResponse.Result.Items.Count == 0)
|
||||
{
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
var rows = jsonResponse.Result.Items;
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var title = row.QuerySelector("a[data-src]").GetAttribute("data-src");
|
||||
if (string.IsNullOrEmpty(title) || title == "0")
|
||||
{
|
||||
title = row.QuerySelector("a[data-src]").TextContent;
|
||||
title = Regex.Replace(title, @"[\[\]\/]", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (title.Length > 5 && title.Substring(title.Length - 5).Contains("."))
|
||||
{
|
||||
title = title.Remove(title.LastIndexOf(".", StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
|
||||
var posterStr = row.QuerySelector("img")?.GetAttribute("src");
|
||||
Uri.TryCreate(posterStr, UriKind.Absolute, out var poster);
|
||||
|
||||
var details = _settings.BaseUrl + row.QuerySelector("a[data-src]").GetAttribute("href");
|
||||
var link = _settings.BaseUrl + row.QuerySelector("a[href*='action=download']").GetAttribute("href");
|
||||
|
||||
var qColSize = row.QuerySelector("td:nth-child(3)");
|
||||
var size = ParseUtil.GetBytes(qColSize.Children[0].TextContent);
|
||||
var files = ParseUtil.CoerceInt(qColSize.Children[1].TextContent.Split(':')[1].Trim());
|
||||
|
||||
var qPublishdate = row.QuerySelector("td:nth-child(4) span");
|
||||
var publishDateStr = qPublishdate.GetAttribute("title");
|
||||
var publishDate = !string.IsNullOrEmpty(publishDateStr) && publishDateStr.Contains(",")
|
||||
? DateTime.ParseExact(publishDateStr, "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture)
|
||||
: DateTime.ParseExact(qPublishdate.TextContent.Trim(), "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture);
|
||||
|
||||
var grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(5)").TextContent);
|
||||
var seeds = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(6)").TextContent);
|
||||
var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(7)").TextContent);
|
||||
var details = _settings.BaseUrl + "torrents.php?id=" + row.TorrentId;
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Title = title,
|
||||
Title = row.ReleaseTitle,
|
||||
Guid = details,
|
||||
InfoUrl = details,
|
||||
PosterUrl = poster?.AbsoluteUri ?? null,
|
||||
DownloadUrl = link,
|
||||
Categories = new List<IndexerCategory> { TvCategoryFromQualityParser.ParseTvShowQuality(title) },
|
||||
Size = size,
|
||||
Files = files,
|
||||
PublishDate = publishDate,
|
||||
Grabs = grabs,
|
||||
Seeders = seeds,
|
||||
Peers = seeds + leechers,
|
||||
PosterUrl = row.Banner,
|
||||
DownloadUrl = row.Download,
|
||||
Categories = new List<IndexerCategory> { TvCategoryFromQualityParser.ParseTvShowQuality(row.ReleaseTitle) },
|
||||
Size = ParseUtil.CoerceLong(row.Size),
|
||||
Files = row.FileList.Length,
|
||||
PublishDate = DateTime.Parse(row.PublishDateUtc, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal),
|
||||
Grabs = ParseUtil.CoerceInt(row.Snatch),
|
||||
Seeders = ParseUtil.CoerceInt(row.Seed),
|
||||
Peers = ParseUtil.CoerceInt(row.Seed) + ParseUtil.CoerceInt(row.Leech),
|
||||
MinimumRatio = 0, // ratioless
|
||||
MinimumSeedTime = 86400, // 24 hours
|
||||
DownloadVolumeFactor = 0, // ratioless tracker
|
||||
@@ -268,14 +217,69 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class NebulanceSettings : UserPassTorrentBaseSettings
|
||||
public class NebulanceSettings : NoAuthTorrentBaseSettings
|
||||
{
|
||||
public NebulanceSettings()
|
||||
{
|
||||
TwoFactorAuth = "";
|
||||
ApiKey = "";
|
||||
}
|
||||
|
||||
[FieldDefinition(4, Label = "Two Factor Auth", HelpText = "Two-Factor Auth")]
|
||||
public string TwoFactorAuth { get; set; }
|
||||
[FieldDefinition(4, Label = "API Key", HelpText = "API Key from User Settings > Api Keys. Key must have List and Download permissions")]
|
||||
public string ApiKey { get; set; }
|
||||
}
|
||||
|
||||
public class NebulanceQuery
|
||||
{
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public string Id { get; set; }
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public string Time { get; set; }
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public string Age { get; set; }
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public int Tvmaze { get; set; }
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public int Imdb { get; set; }
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public string Hash { get; set; }
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public string[] Tags { get; set; }
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public string Name { get; set; }
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public string Category { get; set; }
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public string Series { get; set; }
|
||||
|
||||
public NebulanceQuery Clone()
|
||||
{
|
||||
return MemberwiseClone() as NebulanceQuery;
|
||||
}
|
||||
}
|
||||
|
||||
public class NebulanceTorrent
|
||||
{
|
||||
[JsonProperty(PropertyName = "rls_name")]
|
||||
public string ReleaseTitle { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Size { get; set; }
|
||||
public string Seed { get; set; }
|
||||
public string Leech { get; set; }
|
||||
public string Snatch { get; set; }
|
||||
public string Download { get; set; }
|
||||
[JsonProperty(PropertyName = "file_list")]
|
||||
public string[] FileList { get; set; }
|
||||
[JsonProperty(PropertyName = "series_banner")]
|
||||
public string Banner { get; set; }
|
||||
[JsonProperty(PropertyName = "group_id")]
|
||||
public string TorrentId { get; set; }
|
||||
[JsonProperty(PropertyName = "rls_utc")]
|
||||
public string PublishDateUtc { get; set; }
|
||||
}
|
||||
|
||||
public class NebulanceTorrents
|
||||
{
|
||||
public List<NebulanceTorrent> Items { get; set; }
|
||||
public int Results { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new NewznabRssParser(Settings);
|
||||
return new NewznabRssParser(Settings, Definition, _capabilitiesProvider);
|
||||
}
|
||||
|
||||
public string[] GetBaseUrlFromSettings()
|
||||
@@ -181,13 +181,13 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
}
|
||||
|
||||
if (capabilities.MovieSearchParams != null &&
|
||||
new[] { MovieSearchParam.Q, MovieSearchParam.ImdbId }.Any(v => capabilities.MovieSearchParams.Contains(v)))
|
||||
new[] { MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId, MovieSearchParam.TraktId }.Any(v => capabilities.MovieSearchParams.Contains(v)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (capabilities.TvSearchParams != null &&
|
||||
new[] { TvSearchParam.Q, TvSearchParam.TvdbId, TvSearchParam.TmdbId, TvSearchParam.RId }.Any(v => capabilities.TvSearchParams.Contains(v)) &&
|
||||
new[] { TvSearchParam.Q, TvSearchParam.TvdbId, TvSearchParam.ImdbId, TvSearchParam.TmdbId, TvSearchParam.RId }.Any(v => capabilities.TvSearchParams.Contains(v)) &&
|
||||
new[] { TvSearchParam.Season, TvSearchParam.Ep }.All(v => capabilities.TvSearchParams.Contains(v)))
|
||||
{
|
||||
return null;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
@@ -221,27 +222,59 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
foreach (var xmlCategory in xmlCategories.Elements("category"))
|
||||
{
|
||||
var cat = new IndexerCategory
|
||||
var parentName = xmlCategory.Attribute("name").Value;
|
||||
var parentId = int.Parse(xmlCategory.Attribute("id").Value);
|
||||
|
||||
var mappedCat = NewznabStandardCategory.ParentCats.FirstOrDefault(x => parentName.ToLower().Contains(x.Name.ToLower()));
|
||||
|
||||
if (mappedCat == null)
|
||||
{
|
||||
Id = int.Parse(xmlCategory.Attribute("id").Value),
|
||||
Name = xmlCategory.Attribute("name").Value,
|
||||
Description = xmlCategory.Attribute("description") != null ? xmlCategory.Attribute("description").Value : string.Empty
|
||||
};
|
||||
// Try by parent id if name fails
|
||||
mappedCat = NewznabStandardCategory.ParentCats.FirstOrDefault(x => x.Id == parentId);
|
||||
}
|
||||
|
||||
if (mappedCat == null)
|
||||
{
|
||||
// Fallback to Other
|
||||
mappedCat = NewznabStandardCategory.Other;
|
||||
}
|
||||
|
||||
foreach (var xmlSubcat in xmlCategory.Elements("subcat"))
|
||||
{
|
||||
var subCat = new IndexerCategory
|
||||
{
|
||||
Id = int.Parse(xmlSubcat.Attribute("id").Value),
|
||||
Name = xmlSubcat.Attribute("name").Value,
|
||||
Description = xmlSubcat.Attribute("description") != null ? xmlSubcat.Attribute("description").Value : string.Empty
|
||||
};
|
||||
var subName = xmlSubcat.Attribute("name").Value;
|
||||
var subId = int.Parse(xmlSubcat.Attribute("id").Value);
|
||||
|
||||
cat.SubCategories.Add(subCat);
|
||||
capabilities.Categories.AddCategoryMapping(subCat.Name, subCat);
|
||||
var mappingName = $"{mappedCat.Name}/{subName}";
|
||||
var mappedSubCat = NewznabStandardCategory.AllCats.FirstOrDefault(x => x.Name.ToLower() == mappingName.ToLower());
|
||||
|
||||
if (mappedSubCat == null)
|
||||
{
|
||||
// Try by child id if name fails
|
||||
mappedSubCat = NewznabStandardCategory.AllCats.FirstOrDefault(x => x.Id == subId);
|
||||
}
|
||||
|
||||
if (mappedSubCat == null && mappedCat.Id != NewznabStandardCategory.Other.Id)
|
||||
{
|
||||
// Try by Parent/Other if parent is not other
|
||||
mappedSubCat = NewznabStandardCategory.AllCats.FirstOrDefault(x => x.Name.ToLower() == $"{mappedCat.Name.ToLower()}/other");
|
||||
}
|
||||
|
||||
if (mappedSubCat == null)
|
||||
{
|
||||
// Fallback to Misc Other
|
||||
mappedSubCat = NewznabStandardCategory.OtherMisc;
|
||||
}
|
||||
|
||||
if (mappedSubCat != null)
|
||||
{
|
||||
capabilities.Categories.AddCategoryMapping(subId, mappedSubCat, $"{parentName}/{subName}");
|
||||
}
|
||||
}
|
||||
|
||||
capabilities.Categories.AddCategoryMapping(cat.Name, cat);
|
||||
if (mappedCat != null)
|
||||
{
|
||||
capabilities.Categories.AddCategoryMapping(parentId, mappedCat, parentName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria,
|
||||
capabilities,
|
||||
parameters));
|
||||
|
||||
return pageableRequests;
|
||||
@@ -109,6 +110,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria,
|
||||
capabilities,
|
||||
parameters));
|
||||
|
||||
return pageableRequests;
|
||||
@@ -175,6 +177,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria,
|
||||
capabilities,
|
||||
parameters));
|
||||
|
||||
return pageableRequests;
|
||||
@@ -216,6 +219,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria,
|
||||
capabilities,
|
||||
parameters));
|
||||
|
||||
return pageableRequests;
|
||||
@@ -233,15 +237,15 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
parameters.Add("q", NewsnabifyTitle(searchCriteria.SearchTerm));
|
||||
}
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, parameters));
|
||||
pageableRequests.Add(GetPagedRequests(searchCriteria, capabilities, parameters));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCriteria, NameValueCollection parameters)
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCriteria, IndexerCapabilities capabilities, NameValueCollection parameters)
|
||||
{
|
||||
var baseUrl = string.Format("{0}{1}?t={2}&extended=1", Settings.BaseUrl.TrimEnd('/'), Settings.ApiPath.TrimEnd('/'), searchCriteria.SearchType);
|
||||
var categories = searchCriteria.Categories;
|
||||
var categories = capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
|
||||
|
||||
if (categories != null && categories.Any())
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Xml.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
@@ -13,12 +14,16 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
public const string ns = "{http://www.newznab.com/DTD/2010/feeds/attributes/}";
|
||||
|
||||
private readonly NewznabSettings _settings;
|
||||
private readonly ProviderDefinition _definition;
|
||||
private readonly INewznabCapabilitiesProvider _capabilitiesProvider;
|
||||
|
||||
public NewznabRssParser(NewznabSettings settings)
|
||||
public NewznabRssParser(NewznabSettings settings, ProviderDefinition definition, INewznabCapabilitiesProvider capabilitiesProvider)
|
||||
{
|
||||
PreferredEnclosureMimeTypes = UsenetEnclosureMimeTypes;
|
||||
UseEnclosureUrl = true;
|
||||
_settings = settings;
|
||||
_definition = definition;
|
||||
_capabilitiesProvider = capabilitiesProvider;
|
||||
}
|
||||
|
||||
public static void CheckError(XDocument xdoc, IndexerResponse indexerResponse)
|
||||
@@ -118,19 +123,17 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
|
||||
protected override ICollection<IndexerCategory> GetCategory(XElement item)
|
||||
{
|
||||
var capabilities = _capabilitiesProvider.GetCapabilities(_settings, _definition);
|
||||
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) ?? null;
|
||||
var indexerCat = capabilities.Categories.MapTrackerCatToNewznab(cat);
|
||||
|
||||
if (indexerCat != null)
|
||||
{
|
||||
results.Add(indexerCat);
|
||||
}
|
||||
if (indexerCat != null)
|
||||
{
|
||||
results.AddRange(indexerCat);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
|
||||
public NewznabSettingsValidator()
|
||||
{
|
||||
RuleFor(x => x.BaseSettings).SetValidator(new IndexerCommonSettingsValidator());
|
||||
RuleFor(c => c.BaseUrl).ValidRootUrl();
|
||||
RuleFor(c => c.ApiPath).ValidUrlBase("/api");
|
||||
RuleFor(c => c.ApiKey).NotEmpty().When(ShouldHaveApiKey);
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new TorznabRssParser(Settings);
|
||||
return new TorznabRssParser(Settings, Definition, _capabilitiesProvider);
|
||||
}
|
||||
|
||||
public string[] GetBaseUrlFromSettings()
|
||||
@@ -157,13 +157,13 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
}
|
||||
|
||||
if (capabilities.MovieSearchParams != null &&
|
||||
new[] { MovieSearchParam.Q, MovieSearchParam.ImdbId }.Any(v => capabilities.MovieSearchParams.Contains(v)))
|
||||
new[] { MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId, MovieSearchParam.TraktId }.Any(v => capabilities.MovieSearchParams.Contains(v)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (capabilities.TvSearchParams != null &&
|
||||
new[] { TvSearchParam.Q, TvSearchParam.TvdbId, TvSearchParam.RId }.Any(v => capabilities.TvSearchParams.Contains(v)) &&
|
||||
new[] { TvSearchParam.Q, TvSearchParam.TvdbId, TvSearchParam.ImdbId, TvSearchParam.TmdbId, TvSearchParam.RId }.Any(v => capabilities.TvSearchParams.Contains(v)) &&
|
||||
new[] { TvSearchParam.Season, TvSearchParam.Ep }.All(v => capabilities.TvSearchParams.Contains(v)))
|
||||
{
|
||||
return null;
|
||||
|
||||
@@ -4,7 +4,9 @@ using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Newznab;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Torznab
|
||||
{
|
||||
@@ -13,10 +15,15 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
public const string ns = "{http://torznab.com/schemas/2015/feed}";
|
||||
|
||||
private readonly TorznabSettings _settings;
|
||||
public TorznabRssParser(TorznabSettings settings)
|
||||
private readonly ProviderDefinition _definition;
|
||||
private readonly INewznabCapabilitiesProvider _capabilitiesProvider;
|
||||
|
||||
public TorznabRssParser(TorznabSettings settings, ProviderDefinition definition, INewznabCapabilitiesProvider capabilitiesProvider)
|
||||
{
|
||||
UseEnclosureUrl = true;
|
||||
_settings = settings;
|
||||
_definition = definition;
|
||||
_capabilitiesProvider = capabilitiesProvider;
|
||||
}
|
||||
|
||||
protected override bool PreProcess(IndexerResponse indexerResponse)
|
||||
@@ -157,19 +164,17 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
|
||||
protected override ICollection<IndexerCategory> GetCategory(XElement item)
|
||||
{
|
||||
var capabilities = _capabilitiesProvider.GetCapabilities(_settings, _definition);
|
||||
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) ?? null;
|
||||
var indexerCat = capabilities.Categories.MapTrackerCatToNewznab(cat);
|
||||
|
||||
if (indexerCat != null)
|
||||
{
|
||||
results.Add(indexerCat);
|
||||
}
|
||||
if (indexerCat != null)
|
||||
{
|
||||
results.AddRange(indexerCat);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
|
||||
public TorznabSettingsValidator()
|
||||
{
|
||||
RuleFor(x => x.BaseSettings).SetValidator(new IndexerCommonSettingsValidator());
|
||||
RuleFor(x => x.TorrentBaseSettings).SetValidator(new IndexerTorrentSettingsValidator());
|
||||
RuleFor(c => c.BaseUrl).ValidRootUrl();
|
||||
RuleFor(c => c.ApiPath).ValidUrlBase("/api");
|
||||
RuleFor(c => c.ApiKey).NotEmpty().When(ShouldHaveApiKey);
|
||||
|
||||
@@ -448,7 +448,7 @@ namespace NzbDrone.Core.Indexers
|
||||
|
||||
if (releases.Releases.Empty())
|
||||
{
|
||||
return new ValidationFailure(string.Empty, "Query successful, but no results were returned from your indexer. This may be an issue with the indexer or your indexer category settings.");
|
||||
return new ValidationFailure(string.Empty, "Query successful, but no results were returned from your indexer. This may be an issue with the indexer, your indexer category settings, or other indexer settings such as search freeleech only etc.");
|
||||
}
|
||||
}
|
||||
catch (IndexerAuthException ex)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
@@ -7,6 +9,9 @@ namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public IndexerCommonSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.QueryLimit).GreaterThan(0).When(c => c.QueryLimit.HasValue).WithMessage("Should be greater than zero");
|
||||
|
||||
RuleFor(c => c.GrabLimit).GreaterThan(0).When(c => c.GrabLimit.HasValue).WithMessage("Should be greater than zero");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using NzbDrone.Core.Indexers.Newznab;
|
||||
using NzbDrone.Core.IndexerVersions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.ThingiProvider.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
@@ -17,7 +18,6 @@ namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
List<IIndexer> Enabled(bool filterBlockedIndexers = true);
|
||||
List<IIndexer> AllProviders(bool filterBlockedIndexers = true);
|
||||
void DeleteIndexers(List<int> indexerIds);
|
||||
}
|
||||
|
||||
public class IndexerFactory : ProviderFactory<IIndexer, IndexerDefinition>, IIndexerFactory
|
||||
@@ -256,18 +256,6 @@ namespace NzbDrone.Core.Indexers
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteIndexers(List<int> indexerIds)
|
||||
{
|
||||
var indexersToDelete = _providerRepository.Get(indexerIds).ToList();
|
||||
|
||||
_providerRepository.DeleteMany(indexerIds);
|
||||
|
||||
foreach (var indexer in indexersToDelete)
|
||||
{
|
||||
_logger.Info("Deleted indexer {0}", indexer.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public override ValidationResult Test(IndexerDefinition definition)
|
||||
{
|
||||
var result = base.Test(definition);
|
||||
|
||||
@@ -43,5 +43,8 @@ namespace NzbDrone.Core.Indexers
|
||||
|
||||
[FieldDefinition(2, Type = FieldType.Number, Label = "Seed Time", HelpText = "The time a torrent should be seeded before stopping, empty is app's default", Unit = "minutes", Advanced = true)]
|
||||
public int? SeedTime { get; set; }
|
||||
|
||||
[FieldDefinition(3, Type = FieldType.Number, Label = "Pack Seed Time", HelpText = "The time a pack (season or discography) torrent should be seeded before stopping, empty is app's default", Unit = "minutes", Advanced = true)]
|
||||
public int? PackSeedTime { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace NzbDrone.Core.Indexers.Settings
|
||||
public CookieBaseSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Cookie).NotEmpty();
|
||||
RuleFor(x => x.BaseSettings).SetValidator(new IndexerCommonSettingsValidator());
|
||||
RuleFor(x => x.TorrentBaseSettings).SetValidator(new IndexerTorrentSettingsValidator());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +29,10 @@ namespace NzbDrone.Core.Indexers.Settings
|
||||
[FieldDefinition(2, Label = "Cookie", HelpText = "Site Cookie", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Cookie { get; set; }
|
||||
|
||||
[FieldDefinition(3)]
|
||||
[FieldDefinition(10)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
[FieldDefinition(4)]
|
||||
[FieldDefinition(11)]
|
||||
public IndexerTorrentBaseSettings TorrentBaseSettings { get; set; } = new IndexerTorrentBaseSettings();
|
||||
|
||||
public virtual NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -6,6 +6,11 @@ namespace NzbDrone.Core.Indexers.Settings
|
||||
{
|
||||
public class NoAuthSettingsValidator : AbstractValidator<NoAuthTorrentBaseSettings>
|
||||
{
|
||||
public NoAuthSettingsValidator()
|
||||
{
|
||||
RuleFor(x => x.BaseSettings).SetValidator(new IndexerCommonSettingsValidator());
|
||||
RuleFor(x => x.TorrentBaseSettings).SetValidator(new IndexerTorrentSettingsValidator());
|
||||
}
|
||||
}
|
||||
|
||||
public class NoAuthTorrentBaseSettings : ITorrentIndexerSettings
|
||||
@@ -15,10 +20,10 @@ namespace NzbDrone.Core.Indexers.Settings
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2)]
|
||||
[FieldDefinition(10)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
[FieldDefinition(3)]
|
||||
[FieldDefinition(11)]
|
||||
public IndexerTorrentBaseSettings TorrentBaseSettings { get; set; } = new IndexerTorrentBaseSettings();
|
||||
|
||||
public virtual NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace NzbDrone.Core.Indexers.Settings
|
||||
{
|
||||
RuleFor(c => c.Username).NotEmpty();
|
||||
RuleFor(c => c.Password).NotEmpty();
|
||||
RuleFor(x => x.BaseSettings).SetValidator(new IndexerCommonSettingsValidator());
|
||||
RuleFor(x => x.TorrentBaseSettings).SetValidator(new IndexerTorrentSettingsValidator());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +34,10 @@ namespace NzbDrone.Core.Indexers.Settings
|
||||
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
[FieldDefinition(10)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
[FieldDefinition(5)]
|
||||
[FieldDefinition(11)]
|
||||
public IndexerTorrentBaseSettings TorrentBaseSettings { get; set; } = new IndexerTorrentBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
"BackupRetentionHelpText": "Automatische Backups, die älter als die Aufbewahrungsfrist sind, werden automatisch gelöscht",
|
||||
"Backups": "Backups",
|
||||
"BindAddress": "Adresse binden",
|
||||
"BindAddressHelpText": "Gültige IP-Adresse, \"localhost\" oder \"*\" für alle Netzwerke",
|
||||
"BindAddressHelpText": "Gültige IP Adresse oder \"*\" für alle Netzwerke",
|
||||
"Branch": "Git-Branch",
|
||||
"BypassProxyForLocalAddresses": "Proxy für lokale Adressen umgehen",
|
||||
"CertificateValidation": "Zertifikat Validierung",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"Fixed": "Korjattu",
|
||||
"FocusSearchBox": "Kohdista hakukenttä",
|
||||
"ForMoreInformationOnTheIndividualDownloadClients": "Lue lisää lataustyökalusta painamalla 'Lisätietoja'.",
|
||||
"HideAdvanced": "Piilota lisäasetukset",
|
||||
"HideAdvanced": "Piilota edistyneet",
|
||||
"History": "Historia",
|
||||
"MIA": "Puuttuu",
|
||||
"New": "Uusi",
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"BranchUpdateMechanism": "A külső frissítési mechanizmus által használt ágazat",
|
||||
"BranchUpdate": "Ágazattípus a Prowlarr frissítéseihez",
|
||||
"Branch": "Ágazat",
|
||||
"BindAddressHelpText": "Érvényes IP-cím, localhost, vagy „*” minden interfészhez",
|
||||
"BindAddressHelpText": "Érvényes IP-cím, localhost vagy '*' minden interfészhez",
|
||||
"BindAddress": "Kapcsolási Cím",
|
||||
"BeforeUpdate": "Alkalmazásfrissítés előtt",
|
||||
"Backups": "Biztonsági Mentés",
|
||||
|
||||
@@ -419,5 +419,6 @@
|
||||
"LastExecution": "Laatste Uitvoering",
|
||||
"Queued": "Afwachtend",
|
||||
"Categories": "Categorieën",
|
||||
"AddSyncProfile": "Synchronisatieprofiel toevoegen"
|
||||
"AddSyncProfile": "Synchronisatieprofiel toevoegen",
|
||||
"AudioSearch": "auditief zoeken"
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
"Backups": "Backups",
|
||||
"BeforeUpdate": "Antes da atualização",
|
||||
"BindAddress": "Endereço de vínculo",
|
||||
"BindAddressHelpText": "Endereço IP4 válido ou \"*\" para todas as interfaces",
|
||||
"BindAddressHelpText": "Endereço IP válido, localhost ou '*' para todas as interfaces",
|
||||
"Branch": "Ramificação",
|
||||
"BranchUpdate": "Ramificação para atualização do Prowlarr",
|
||||
"BypassProxyForLocalAddresses": "Ignorar proxy para endereços locais",
|
||||
@@ -447,7 +447,7 @@
|
||||
"AddSyncProfile": "Adicionar Perfil de Sincronização",
|
||||
"MinimumSeeders": "Mínimo de Seeders",
|
||||
"MinimumSeedersHelpText": "Semeadores mínimos exigidos pelo aplicativo para o indexador baixar",
|
||||
"ThemeHelpText": "Altere o tema da IU do Prowlarr, inspirado em {0}",
|
||||
"ThemeHelpText": "Alterar o tema da interface do usuário do aplicativo, o tema 'Auto' usará o tema do sistema operacional para definir o modo Claro ou Escuro. Inspirado por {0}",
|
||||
"InstanceName": "Nome da instância",
|
||||
"InstanceNameHelpText": "Nome da instância na guia e para o nome do aplicativo Syslog",
|
||||
"Duration": "Duração",
|
||||
@@ -462,5 +462,9 @@
|
||||
"NextExecution": "Próxima Execução",
|
||||
"Started": "Iniciado",
|
||||
"ApplicationLongTermStatusCheckAllClientMessage": "Todos os aplicativos estão indisponíveis devido a falhas por mais de 6 horas",
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "Aplicativos indisponíveis devido a falhas por mais de 6 horas: {0}"
|
||||
"ApplicationLongTermStatusCheckSingleClientMessage": "Aplicativos indisponíveis devido a falhas por mais de 6 horas: {0}",
|
||||
"AreYouSureYouWantToDeleteCategory": "Tem certeza de que deseja excluir a categoria mapeada?",
|
||||
"DeleteClientCategory": "Excluir Categoria de Cliente de Download",
|
||||
"DownloadClientCategory": "Categoria de Download do Cliente",
|
||||
"MappedCategories": "Categorias Mapeadas"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace NzbDrone.Core.Parser
|
||||
if (DateTimeRoutines.TryParseDateOrTime(
|
||||
str, dtFormat, out DateTimeRoutines.ParsedDateTime dt))
|
||||
{
|
||||
return dt.DateTime;
|
||||
return dt.DateTime.ToUniversalTime();
|
||||
}
|
||||
|
||||
throw new InvalidDateException($"FromFuzzyTime parsing failed for string {str}");
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Messaging;
|
||||
|
||||
namespace NzbDrone.Core.ThingiProvider.Events
|
||||
{
|
||||
public class ProviderBulkDeletedEvent<TProvider> : IEvent
|
||||
{
|
||||
public IEnumerable<int> ProviderIds { get; private set; }
|
||||
|
||||
public ProviderBulkDeletedEvent(IEnumerable<int> ids)
|
||||
{
|
||||
ProviderIds = ids;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ namespace NzbDrone.Core.ThingiProvider
|
||||
void Update(TProviderDefinition definition);
|
||||
void Update(IEnumerable<TProviderDefinition> definitions);
|
||||
void Delete(int id);
|
||||
void Delete(IEnumerable<int> ids);
|
||||
IEnumerable<TProviderDefinition> GetDefaultDefinitions();
|
||||
IEnumerable<TProviderDefinition> GetPresetDefinitions(TProviderDefinition providerDefinition);
|
||||
void SetProviderCharacteristics(TProviderDefinition definition);
|
||||
|
||||
@@ -121,6 +121,13 @@ namespace NzbDrone.Core.ThingiProvider
|
||||
_eventAggregator.PublishEvent(new ProviderDeletedEvent<TProvider>(id));
|
||||
}
|
||||
|
||||
public void Delete(IEnumerable<int> ids)
|
||||
{
|
||||
_providerRepository.DeleteMany(ids);
|
||||
|
||||
_eventAggregator.PublishEvent(new ProviderBulkDeletedEvent<TProvider>(ids));
|
||||
}
|
||||
|
||||
public TProvider GetInstance(TProviderDefinition definition)
|
||||
{
|
||||
var type = GetImplementation(definition);
|
||||
|
||||
@@ -11,13 +11,13 @@ namespace Prowlarr.Api.V1.Indexers
|
||||
[V1ApiController("indexer/editor")]
|
||||
public class IndexerEditorController : Controller
|
||||
{
|
||||
private readonly IIndexerFactory _indexerService;
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
private readonly IManageCommandQueue _commandQueueManager;
|
||||
private readonly IndexerResourceMapper _resourceMapper;
|
||||
|
||||
public IndexerEditorController(IIndexerFactory indexerService, IManageCommandQueue commandQueueManager, IndexerResourceMapper resourceMapper)
|
||||
public IndexerEditorController(IIndexerFactory indexerFactory, IManageCommandQueue commandQueueManager, IndexerResourceMapper resourceMapper)
|
||||
{
|
||||
_indexerService = indexerService;
|
||||
_indexerFactory = indexerFactory;
|
||||
_commandQueueManager = commandQueueManager;
|
||||
_resourceMapper = resourceMapper;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ namespace Prowlarr.Api.V1.Indexers
|
||||
[HttpPut]
|
||||
public IActionResult SaveAll(IndexerEditorResource resource)
|
||||
{
|
||||
var indexersToUpdate = _indexerService.AllProviders(false).Select(x => (IndexerDefinition)x.Definition).Where(d => resource.IndexerIds.Contains(d.Id));
|
||||
var indexersToUpdate = _indexerFactory.AllProviders(false).Select(x => (IndexerDefinition)x.Definition).Where(d => resource.IndexerIds.Contains(d.Id));
|
||||
|
||||
foreach (var indexer in indexersToUpdate)
|
||||
{
|
||||
@@ -59,13 +59,13 @@ namespace Prowlarr.Api.V1.Indexers
|
||||
}
|
||||
}
|
||||
|
||||
_indexerService.Update(indexersToUpdate);
|
||||
_indexerFactory.Update(indexersToUpdate);
|
||||
|
||||
var indexers = _indexerService.All();
|
||||
var indexers = _indexerFactory.All();
|
||||
|
||||
foreach (var definition in indexers)
|
||||
{
|
||||
_indexerService.SetProviderCharacteristics(definition);
|
||||
_indexerFactory.SetProviderCharacteristics(definition);
|
||||
}
|
||||
|
||||
return Accepted(_resourceMapper.ToResource(indexers));
|
||||
@@ -74,7 +74,7 @@ namespace Prowlarr.Api.V1.Indexers
|
||||
[HttpDelete]
|
||||
public object DeleteIndexers([FromBody] IndexerEditorResource resource)
|
||||
{
|
||||
_indexerService.DeleteIndexers(resource.IndexerIds);
|
||||
_indexerFactory.Delete(resource.IndexerIds);
|
||||
|
||||
return new { };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user