parent
5e7f4f3fc1
commit
ba1c1baeb5
@ -0,0 +1,317 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Net;
|
||||
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.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
|
||||
{
|
||||
public class InternetArchive : TorrentIndexerBase<InternetArchiveSettings>
|
||||
{
|
||||
public override string Name => "Internet Archive";
|
||||
|
||||
public override string[] IndexerUrls => new string[] { "https://archive.org/" };
|
||||
|
||||
public override string Description => "Internet Archive is a non-profit library of millions of free books, movies, software, music, websites, and more.";
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public InternetArchive(IHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new InternetArchiveParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new InternetArchiveRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
// c.f. https://archive.org/services/docs/api/metadata-schema/index.html?highlight=mediatype#mediatype
|
||||
caps.Categories.AddCategoryMapping("texts", NewznabStandardCategory.Books);
|
||||
caps.Categories.AddCategoryMapping("etree", NewznabStandardCategory.Audio);
|
||||
caps.Categories.AddCategoryMapping("audio", NewznabStandardCategory.Audio);
|
||||
caps.Categories.AddCategoryMapping("movies", NewznabStandardCategory.Movies);
|
||||
caps.Categories.AddCategoryMapping("software", NewznabStandardCategory.PC);
|
||||
caps.Categories.AddCategoryMapping("image", NewznabStandardCategory.OtherMisc);
|
||||
caps.Categories.AddCategoryMapping("data", NewznabStandardCategory.Other);
|
||||
caps.Categories.AddCategoryMapping("web", NewznabStandardCategory.Other);
|
||||
caps.Categories.AddCategoryMapping("collection", NewznabStandardCategory.Other);
|
||||
caps.Categories.AddCategoryMapping("account", NewznabStandardCategory.Other);
|
||||
|
||||
caps.Categories.AddCategoryMapping("other", NewznabStandardCategory.Other);
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class InternetArchiveRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public InternetArchiveSettings Settings { get; set; }
|
||||
public IndexerCapabilities Capabilities { get; set; }
|
||||
|
||||
public InternetArchiveRequestGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string searchTerm, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var query = "format:(\"Archive BitTorrent\")";
|
||||
|
||||
if (searchTerm.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
if (Settings.TitleOnly)
|
||||
{
|
||||
query = string.Format("title:({0}) AND {1}", searchTerm, query);
|
||||
}
|
||||
else
|
||||
{
|
||||
query = string.Format("{0} AND {1}", searchTerm, query);
|
||||
}
|
||||
}
|
||||
|
||||
var categories = Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
|
||||
if (categories.Count > 0)
|
||||
{
|
||||
query = string.Format("{0} AND mediatype:({1})", query, string.Join(" OR ", categories));
|
||||
}
|
||||
|
||||
string sortBy = (InternetArchiveSort)Settings.SortBy switch
|
||||
{
|
||||
InternetArchiveSort.PublicDate => "publicdate",
|
||||
InternetArchiveSort.Downloads => "downloads",
|
||||
InternetArchiveSort.Size => "item_size",
|
||||
_ => "publicdate",
|
||||
};
|
||||
|
||||
string sortOrder = (InternetArchiveSortOrder)Settings.SortOrder switch
|
||||
{
|
||||
InternetArchiveSortOrder.Descending => "desc",
|
||||
InternetArchiveSortOrder.Ascending => "asc",
|
||||
_ => "desc",
|
||||
};
|
||||
|
||||
var parameters = new NameValueCollection
|
||||
{
|
||||
{ "q", query },
|
||||
{ "fields", "btih,downloads,identifier,item_size,mediatype,publicdate,title" },
|
||||
{ "count", searchCriteria.Limit.GetValueOrDefault(100).ToString() }, // API default is 5000, don't think thats viable at all.
|
||||
{ "sorts", string.Format("{0} {1}", sortBy, sortOrder) }
|
||||
};
|
||||
|
||||
var searchUrl = string.Format("{0}/services/search/v1/scrape?{1}", Settings.BaseUrl.TrimEnd('/'), parameters.GetQueryString());
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Json);
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria));
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria));
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria));
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria));
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria));
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class InternetArchiveParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly InternetArchiveSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
public InternetArchiveParser(InternetArchiveSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
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<InternetArchiveResponse>(indexerResponse.HttpResponse);
|
||||
|
||||
foreach (var searchResult in jsonResponse.Resource.SearchResults)
|
||||
{
|
||||
var title = searchResult.Title ?? searchResult.Identifier;
|
||||
|
||||
var downloadUrl = string.Format("{0}/download/{1}/{1}_archive.torrent", _settings.BaseUrl.TrimEnd('/'), searchResult.Identifier);
|
||||
var detailsUrl = string.Format("{0}/details/{1}", _settings.BaseUrl.TrimEnd('/'), searchResult.Identifier);
|
||||
|
||||
var category = _categories.MapTrackerCatToNewznab(searchResult.MediaType);
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Categories = category,
|
||||
CommentUrl = detailsUrl,
|
||||
DownloadUrl = downloadUrl,
|
||||
DownloadVolumeFactor = 0,
|
||||
Guid = detailsUrl,
|
||||
Grabs = searchResult.Downloads,
|
||||
InfoHash = searchResult.InfoHash,
|
||||
InfoUrl = detailsUrl,
|
||||
MagnetUrl = MagnetLinkBuilder.BuildPublicMagnetLink(searchResult.InfoHash, title),
|
||||
Peers = 2,
|
||||
PublishDate = searchResult.PublicDate,
|
||||
Seeders = 1,
|
||||
Size = searchResult.Size,
|
||||
Title = title,
|
||||
UploadVolumeFactor = 1
|
||||
};
|
||||
|
||||
torrentInfos.Add(release);
|
||||
}
|
||||
|
||||
return torrentInfos.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public class InternetArchiveResponse
|
||||
{
|
||||
[JsonProperty(PropertyName = "items")]
|
||||
public List<InternetArchiveTorrent> SearchResults { get; set; }
|
||||
|
||||
public string Cursor { get; set; }
|
||||
public int Count { get; set; }
|
||||
public int Total { get; set; }
|
||||
}
|
||||
|
||||
public class InternetArchiveTorrent
|
||||
{
|
||||
public int Downloads { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "btih")]
|
||||
public string InfoHash { get; set; }
|
||||
public string MediaType { get; set; }
|
||||
public DateTime PublicDate { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "item_size")]
|
||||
public long Size { get; set; }
|
||||
public string Title { get; set; }
|
||||
}
|
||||
|
||||
public class InternetArchiveSettings : IIndexerSettings
|
||||
{
|
||||
[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, Label = "Sort By", Type = FieldType.Select, Advanced = true, SelectOptions = typeof(InternetArchiveSort), HelpText = "Field used to sort the search results.")]
|
||||
public int SortBy { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Sort Order", Type = FieldType.Select, Advanced = true, SelectOptions = typeof(InternetArchiveSortOrder), HelpText = "Order to use when sorting results.")]
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Title Only", Type = FieldType.Checkbox, Advanced = true, HelpText = "Whether to search in title only.")]
|
||||
public bool TitleOnly { get; set; }
|
||||
|
||||
[FieldDefinition(5)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public InternetArchiveSettings()
|
||||
{
|
||||
SortBy = (int)InternetArchiveSort.PublicDate;
|
||||
SortOrder = (int)InternetArchiveSortOrder.Descending;
|
||||
TitleOnly = false;
|
||||
}
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult();
|
||||
}
|
||||
}
|
||||
|
||||
public enum InternetArchiveSort
|
||||
{
|
||||
PublicDate,
|
||||
Downloads,
|
||||
Size
|
||||
}
|
||||
|
||||
public enum InternetArchiveSortOrder
|
||||
{
|
||||
Descending,
|
||||
Ascending
|
||||
}
|
||||
}
|
Loading…
Reference in new issue