New: (Indexers) Add Support for Unit3d API / Convert Blutopia

This commit is contained in:
Qstick
2021-04-20 23:58:03 -04:00
parent 639d638135
commit c1ab99a425
6 changed files with 422 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.UNIT3D;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions
{
public class Blutopia : Unit3dBase
{
public override string Name => "Blutopia";
public override string BaseUrl => "https://blutopia.xyz/";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public Blutopia(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
protected override IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Movie");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TV, "TV Show");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.MoviesOther, "FANRES");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.MoviesOther, "Trailer");
return caps;
}
}
}

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace NzbDrone.Core.Indexers.Definitions.UNIT3D
{
public class Unit3dResponse
{
public List<Unit3dTorrent> Data { get; set; }
public Unit3dLinks Links { get; set; }
}
public class Unit3dTorrent
{
public string Type { get; set; }
public string Id { get; set; }
public Unit3dTorrentAttributes Attributes { get; set; }
}
public class Unit3dTorrentAttributes
{
public string Name { get; set; }
[JsonProperty(PropertyName = "release_year")]
public int ReleaseYear { get; set; }
public string Category { get; set; }
public string Encode { get; set; }
public string Resolution { get; set; }
public long Size { get; set; }
[JsonProperty(PropertyName = "num_file")]
public int Files { get; set; }
[JsonProperty(PropertyName = "times_completed")]
public int Grabs { get; set; }
public int Seeders { get; set; }
public int Leechers { get; set; }
[JsonProperty(PropertyName = "created_at")]
public string CreatedAt { get; set; }
[JsonProperty(PropertyName = "download_link")]
public string DownloadLink { get; set; }
[JsonProperty(PropertyName = "details_link")]
public string DetailsLink { get; set; }
[JsonProperty(PropertyName = "imdb_id")]
public string ImdbId { get; set; }
[JsonProperty(PropertyName = "tmdb_id")]
public string TmdbId { get; set; }
[JsonProperty(PropertyName = "tvdb_id")]
public string TvdbId { get; set; }
[JsonProperty(PropertyName = "igdb_id")]
public string IgdbId { get; set; }
[JsonProperty(PropertyName = "mal_id")]
public string MalId { get; set; }
[JsonProperty(PropertyName = "double_upload")]
public bool DoubleUpload { get; set; }
public bool Freeleech { get; set; }
public string Uploader { get; set; }
}
public class Unit3dLinks
{
public string first { get; set; }
public string last { get; set; }
public string prev { get; set; }
public string next { get; set; }
public string self { get; set; }
}
}

View File

@@ -0,0 +1,54 @@
using System;
using System.Net;
using System.Threading.Tasks;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions.UNIT3D
{
public abstract class Unit3dBase : HttpIndexerBase<Unit3dSettings>
{
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override string BaseUrl => "";
public override bool SupportsRss => true;
public override bool SupportsSearch => true;
public override int PageSize => 50;
public override IndexerCapabilities Capabilities => SetCapabilities();
public Unit3dBase(IHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new Unit3dRequestGenerator()
{
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
Capabilities = Capabilities,
BaseUrl = BaseUrl
};
}
public override IParseIndexerResponse GetParser()
{
return new Unit3dParser(Capabilities.Categories, BaseUrl);
}
protected virtual IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities();
return caps;
}
}
}

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions.UNIT3D
{
public class Unit3dParser : IParseIndexerResponse
{
private readonly string _baseUrl;
private readonly IndexerCapabilitiesCategories _categories;
protected virtual string TorrentUrl => _baseUrl + "torrents";
public Unit3dParser(IndexerCapabilitiesCategories categories, string baseUrl)
{
_categories = categories;
_baseUrl = baseUrl;
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<TorrentInfo>();
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
}
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
{
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
}
var jsonResponse = new HttpResponse<Unit3dResponse>(indexerResponse.HttpResponse);
foreach (var row in jsonResponse.Resource.Data)
{
var details = row.Attributes.DetailsLink;
var link = row.Attributes.DownloadLink;
var release = new TorrentInfo
{
Title = row.Attributes.Name,
DownloadUrl = link,
InfoHash = row.Id,
InfoUrl = details,
Guid = details,
Category = _categories.MapTrackerCatDescToNewznab(row.Attributes.Category),
PublishDate = DateTime.Parse(row.Attributes.CreatedAt, CultureInfo.InvariantCulture),
Size = row.Attributes.Size,
Files = row.Attributes.Files,
Grabs = row.Attributes.Grabs,
Seeders = row.Attributes.Seeders,
ImdbId = ParseUtil.GetImdbID(row.Attributes.ImdbId).GetValueOrDefault(),
TmdbId = row.Attributes.TmdbId.IsNullOrWhiteSpace() ? 0 : ParseUtil.CoerceInt(row.Attributes.TmdbId),
TvdbId = row.Attributes.TvdbId.IsNullOrWhiteSpace() ? 0 : ParseUtil.CoerceInt(row.Attributes.TvdbId),
Peers = row.Attributes.Leechers + row.Attributes.Seeders,
DownloadVolumeFactor = row.Attributes.Freeleech ? 0 : 1,
UploadVolumeFactor = row.Attributes.DoubleUpload ? 2 : 1,
MinimumRatio = 1,
MinimumSeedTime = 172800, // 48 hours
};
torrentInfos.Add(release);
}
// order by date
return torrentInfos.OrderByDescending(o => o.PublishDate).ToArray();
}
}
}

View File

@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.Indexers.Definitions.UNIT3D
{
public class Unit3dRequestGenerator : IIndexerRequestGenerator
{
public Unit3dSettings Settings { get; set; }
public string BaseUrl { get; set; }
public IHttpClient HttpClient { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public Logger Logger { get; set; }
protected virtual string SearchUrl => BaseUrl + "api/torrents/filter";
protected virtual bool ImdbInTags => false;
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var parameters = GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
if (searchCriteria.ImdbId != null)
{
parameters.Add("imdb", searchCriteria.ImdbId);
}
if (searchCriteria.TmdbId > 0)
{
parameters.Add("tmdb", searchCriteria.TmdbId.ToString());
}
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
var parameters = GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var parameters = GetBasicSearchParameters(searchCriteria.SanitizedTvSearchString, searchCriteria.Categories);
if (searchCriteria.ImdbId != null)
{
parameters.Add("imdb", searchCriteria.ImdbId);
}
if (searchCriteria.TvdbId > 0)
{
parameters.Add("tmdb", searchCriteria.TvdbId.ToString());
}
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
var parameters = GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var parameters = GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
}
private IEnumerable<IndexerRequest> GetRequest(List<KeyValuePair<string, string>> searchParameters)
{
var searchUrl = SearchUrl + "?" + searchParameters.GetQueryString();
var request = new IndexerRequest(searchUrl, HttpAccept.Json);
yield return request;
}
private List<KeyValuePair<string, string>> GetBasicSearchParameters(string searchTerm, int[] categories)
{
var searchString = searchTerm;
var qc = new List<KeyValuePair<string, string>>
{
{ "api_token", Settings.ApiKey }
};
if (!string.IsNullOrWhiteSpace(searchString))
{
qc.Add("name", searchString);
}
if (categories != null)
{
foreach (var cat in Capabilities.Categories.MapTorznabCapsToTrackers(categories))
{
qc.Add("categories[]", cat);
}
}
return qc;
}
}
}

View File

@@ -0,0 +1,32 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions.UNIT3D
{
public class Unit3dSettingsValidator : AbstractValidator<Unit3dSettings>
{
public Unit3dSettingsValidator()
{
RuleFor(c => c.ApiKey).NotEmpty();
}
}
public class Unit3dSettings : IProviderConfig
{
private static readonly Unit3dSettingsValidator Validator = new Unit3dSettingsValidator();
public Unit3dSettings()
{
}
[FieldDefinition(1, Label = "Api Key", HelpText = "Api key generated in My Security", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}