mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-04-24 13:57:11 -04:00
New: (Indexer) SubsPlease
This commit is contained in:
parent
b9f7a6b91c
commit
4545e38445
2 changed files with 286 additions and 11 deletions
285
src/NzbDrone.Core/Indexers/Definitions/SubsPlease.cs
Normal file
285
src/NzbDrone.Core/Indexers/Definitions/SubsPlease.cs
Normal file
|
@ -0,0 +1,285 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class SubsPlease : HttpIndexerBase<SubsPleaseSettings>
|
||||
{
|
||||
public override string Name => "SubsPlease";
|
||||
public override string BaseUrl => "https://subsplease.org/";
|
||||
public override string Language => "en-us";
|
||||
public override string Description => "SubsPlease - A better HorribleSubs/Erai replacement";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public SubsPlease(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new SubsPleaseRequestGenerator() { Settings = Settings, Capabilities = Capabilities, BaseUrl = BaseUrl };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new SubsPleaseParser(Settings, Capabilities.Categories, BaseUrl);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "Anime");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class SubsPleaseRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public SubsPleaseSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
public SubsPleaseRequestGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetSearchRequests(string term)
|
||||
{
|
||||
var searchUrl = string.Format("{0}/api/?", BaseUrl.TrimEnd('/'));
|
||||
|
||||
string searchTerm = Regex.Replace(term, "\\[?SubsPlease\\]?\\s*", string.Empty, RegexOptions.IgnoreCase).Trim();
|
||||
|
||||
// If the search terms contain a resolution, remove it from the query sent to the API
|
||||
Match resMatch = Regex.Match(searchTerm, "\\d{3,4}[p|P]");
|
||||
if (resMatch.Success)
|
||||
{
|
||||
searchTerm = searchTerm.Replace(resMatch.Value, string.Empty);
|
||||
}
|
||||
|
||||
var queryParameters = new NameValueCollection
|
||||
{
|
||||
{ "f", "search" },
|
||||
{ "tz", "America/New_York" },
|
||||
{ "s", searchTerm }
|
||||
};
|
||||
|
||||
var request = new IndexerRequest(searchUrl + queryParameters.GetQueryString(), HttpAccept.Json);
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetRssRequest()
|
||||
{
|
||||
var searchUrl = string.Format("{0}/api/?", BaseUrl.TrimEnd('/'));
|
||||
|
||||
var queryParameters = new NameValueCollection
|
||||
{
|
||||
{ "f", "latest" },
|
||||
{ "tz", "America/New_York" }
|
||||
};
|
||||
|
||||
var request = new IndexerRequest(searchUrl + queryParameters.GetQueryString(), HttpAccept.Json);
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
if (searchCriteria.RssSearch)
|
||||
{
|
||||
pageableRequests.Add(GetRssRequest());
|
||||
}
|
||||
else
|
||||
{
|
||||
pageableRequests.Add(GetSearchRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString)));
|
||||
}
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
if (searchCriteria.RssSearch)
|
||||
{
|
||||
pageableRequests.Add(GetRssRequest());
|
||||
}
|
||||
else
|
||||
{
|
||||
pageableRequests.Add(GetSearchRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm)));
|
||||
}
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class SubsPleaseParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly SubsPleaseSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public SubsPleaseParser(SubsPleaseSettings settings, IndexerCapabilitiesCategories categories, string baseurl)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
_baseUrl = baseurl;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
|
||||
}
|
||||
|
||||
// When there are no results, the API returns an empty array or empty response instead of an object
|
||||
if (string.IsNullOrWhiteSpace(indexerResponse.Content) || indexerResponse.Content == "[]")
|
||||
{
|
||||
return torrentInfos;
|
||||
}
|
||||
|
||||
var jsonResponse = new HttpResponse<Dictionary<string, SubPleaseRelease>>(indexerResponse.HttpResponse);
|
||||
|
||||
foreach (var keyValue in jsonResponse.Resource)
|
||||
{
|
||||
SubPleaseRelease r = keyValue.Value;
|
||||
|
||||
foreach (var d in r.Downloads)
|
||||
{
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
InfoUrl = _baseUrl + $"shows/{r.Page}/",
|
||||
PublishDate = r.Release_Date.DateTime,
|
||||
Files = 1,
|
||||
Category = new List<IndexerCategory> { NewznabStandardCategory.TVAnime },
|
||||
Seeders = 1,
|
||||
Peers = 2,
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 172800, // 48 hours
|
||||
DownloadVolumeFactor = 0,
|
||||
UploadVolumeFactor = 1
|
||||
};
|
||||
|
||||
// Ex: [SubsPlease] Shingeki no Kyojin (The Final Season) - 64 (1080p)
|
||||
release.Title += $"[SubsPlease] {r.Show} - {r.Episode} ({d.Res}p)";
|
||||
release.MagnetUrl = d.Magnet;
|
||||
release.DownloadUrl = null;
|
||||
release.Guid = d.Magnet;
|
||||
|
||||
// The API doesn't tell us file size, so give an estimate based on resolution
|
||||
if (string.Equals(d.Res, "1080"))
|
||||
{
|
||||
release.Size = 1395864371; // 1.3GB
|
||||
}
|
||||
else if (string.Equals(d.Res, "720"))
|
||||
{
|
||||
release.Size = 734003200; // 700MB
|
||||
}
|
||||
else if (string.Equals(d.Res, "480"))
|
||||
{
|
||||
release.Size = 367001600; // 350MB
|
||||
}
|
||||
else
|
||||
{
|
||||
release.Size = 1073741824; // 1GB
|
||||
}
|
||||
|
||||
torrentInfos.Add(release);
|
||||
}
|
||||
}
|
||||
|
||||
return torrentInfos.ToArray();
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class SubsPleaseSettingsValidator : AbstractValidator<SubsPleaseSettings>
|
||||
{
|
||||
}
|
||||
|
||||
public class SubsPleaseSettings : IProviderConfig
|
||||
{
|
||||
private static readonly SubsPleaseSettingsValidator Validator = new SubsPleaseSettingsValidator();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
|
||||
public class SubPleaseRelease
|
||||
{
|
||||
public string Time { get; set; }
|
||||
public DateTimeOffset Release_Date { get; set; }
|
||||
public string Show { get; set; }
|
||||
public string Episode { get; set; }
|
||||
public SubPleaseDownloadInfo[] Downloads { get; set; }
|
||||
public string Xdcc { get; set; }
|
||||
public string ImageUrl { get; set; }
|
||||
public string Page { get; set; }
|
||||
}
|
||||
|
||||
public class SubPleaseDownloadInfo
|
||||
{
|
||||
public string Res { get; set; }
|
||||
public string Magnet { get; set; }
|
||||
}
|
||||
}
|
|
@ -252,7 +252,7 @@ namespace NzbDrone.Core.Indexers
|
|||
}
|
||||
}
|
||||
|
||||
releases.AddRange(pagedReleases.Where(IsValidRelease));
|
||||
releases.AddRange(pagedReleases);
|
||||
}
|
||||
|
||||
if (releases.Any())
|
||||
|
@ -363,16 +363,6 @@ namespace NzbDrone.Core.Indexers
|
|||
return Capabilities ?? ((IndexerDefinition)Definition).Capabilities;
|
||||
}
|
||||
|
||||
protected virtual bool IsValidRelease(ReleaseInfo release)
|
||||
{
|
||||
if (release.DownloadUrl == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool IsFullPage(IList<ReleaseInfo> page)
|
||||
{
|
||||
return PageSize != 0 && page.Count >= PageSize;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue