From 42bc31c324dc82cc2a7de9062988e16b75d727b0 Mon Sep 17 00:00:00 2001 From: Garfield69 Date: Fri, 28 Oct 2022 14:38:30 +1300 Subject: [PATCH] add nebulanceAPI. resolves #12125 --- src/Jackett.Common/Indexers/NebulanceAPI.cs | 248 ++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 src/Jackett.Common/Indexers/NebulanceAPI.cs diff --git a/src/Jackett.Common/Indexers/NebulanceAPI.cs b/src/Jackett.Common/Indexers/NebulanceAPI.cs new file mode 100644 index 000000000..e192874a2 --- /dev/null +++ b/src/Jackett.Common/Indexers/NebulanceAPI.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Jackett.Common.Models; +using Jackett.Common.Models.IndexerConfig; +using Jackett.Common.Services.Interfaces; +using Jackett.Common.Utils.Clients; +using Microsoft.AspNetCore.Http.Internal; +using Newtonsoft.Json.Linq; +using NLog; +using static Jackett.Common.Models.IndexerConfig.ConfigurationData; + +namespace Jackett.Common.Indexers +{ + [ExcludeFromCodeCoverage] + public class NebulanceAPI : BaseWebIndexer + { + // Docs at https://nebulance.io/articles.php?topic=api_key + protected virtual string APIUrl => SiteLink + "api.php"; + protected virtual int KeyLength => 32; + + // TODO: remove ConfigurationDataAPIKey class and use ConfigurationDataPasskey instead + private new ConfigurationDataAPIKey configData + { + get => (ConfigurationDataAPIKey)base.configData; + set => base.configData = value; + } + + public NebulanceAPI(IIndexerConfigurationService configService, WebClient wc, Logger l, + IProtectionService ps, ICacheService cs) + : base(id: "nebulanceapi", + name: "NebulanceAPI", + description: "At Nebulance we will change the way you think about TV. Using API.", + link: "https://nebulance.io/", + caps: new TorznabCapabilities + { + LimitsDefault = 100, + LimitsMax = 1000, + TvSearchParams = new List + { + TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.Genre + } + }, + configService: configService, + client: wc, + logger: l, + p: ps, + cacheService: cs, + configData: new ConfigurationDataAPIKey()) + { + Encoding = Encoding.UTF8; + Language = "en-US"; + Type = "private"; + + AddCategoryMapping("tv", TorznabCatType.TV, "tv"); + AddCategoryMapping("sd", TorznabCatType.TVSD, "sd"); + AddCategoryMapping("hd", TorznabCatType.TVHD, "hd"); + AddCategoryMapping("uhd", TorznabCatType.TVUHD, "uhd"); + AddCategoryMapping("4k", TorznabCatType.TVUHD, "4k"); + AddCategoryMapping("480p", TorznabCatType.TVSD, "480p"); + AddCategoryMapping("720p", TorznabCatType.TVHD, "720p"); + AddCategoryMapping("1080p", TorznabCatType.TVHD, "1080p"); + AddCategoryMapping("1080i", TorznabCatType.TVHD, "1080i"); + AddCategoryMapping("2160p", TorznabCatType.TVUHD, "2160p"); + + configData.AddDynamic("keyInfo", new DisplayInfoConfigurationItem(String.Empty, "Generate a new key by accessing your account profile settings at Nebulance, scroll down to the API Keys section, tick the New Key, list and download checkboxes and save.")); + + } + + public override async Task ApplyConfiguration(JToken configJson) + { + LoadValuesFromJson(configJson); + + IsConfigured = false; + var apiKey = configData.Key; + if (apiKey.Value.Length != KeyLength) + throw new Exception($"Invalid API Key configured: expected length: {KeyLength}, got {apiKey.Value.Length}"); + + try + { + var results = await PerformQuery(new TorznabQuery()); + if (!results.Any()) + throw new Exception("Testing returned no results!"); + IsConfigured = true; + SaveConfig(); + } + catch (Exception e) + { + throw new ExceptionWithConfigData(e.Message, configData); + } + + return IndexerConfigurationStatus.Completed; + } + + private string JsonRPCRequest(string method, JArray parameters) + { + dynamic request = new JObject(); + request["jsonrpc"] = "2.0"; + request["method"] = method; + request["params"] = parameters; + request["id"] = Guid.NewGuid().ToString().Substring(0, 8); + return request.ToString(); + } + + protected override async Task> PerformQuery(TorznabQuery query) + { + var ValidList = new List() { + "action", + "adventure", + "children", + "biography", + "comedy", + "crime", + "documentary", + "drama", + "family", + "fantasy", + "game-show", + "history", + "horror", + "medical", + "music", + "musical", + "mystery", + "news", + "reality-tv", + "romance", + "sci-fi", + "sitcom", + "sport", + "talk-show", + "thriller", + "travel", + "war", + "western" + }; + var ValidCats = new List() { + "sd", + "hd", + "uhd", + "4k", + "480p", + "720p", + "1080i", + "1080p", + "2160p" + }; + + var searchParam = new JObject(); + var searchString = query.GetQueryString(); + + if (!string.IsNullOrWhiteSpace(searchString)) + { + searchParam["name"] = "%" + Regex.Replace(searchString, @"[ -._]", "%").Trim() + "%"; + } + else + { + searchParam["name"] = "%"; + } + if (query.IsGenreQuery) + { + var genre = new JArray + { + new JValue(query.Genre) + }; + searchParam["tags"] = genre; + } + var limit = query.Limit; + if (limit == 0) + limit = (int)TorznabCaps.LimitsDefault; + var offset = query.Offset; + var releases = new List(); + + var parameters = new JArray + { + new JValue(configData.Key.Value), + JObject.FromObject(searchParam), + new JValue(limit), + new JValue(offset) + }; + + var response = await RequestWithCookiesAndRetryAsync( + APIUrl, method: RequestType.POST, + headers: new Dictionary + { + {"Accept", "application/json-rpc, application/json"}, + {"Content-Type", "application/json-rpc"} + }, rawbody: JsonRPCRequest("getTorrents", parameters), emulateBrowser: false); + + if (response.ContentString != null && response.ContentString.Contains("Invalid params")) + throw new Exception($"Invalid API Key configured"); + try + { + var json = JObject.Parse(response.ContentString); + foreach (var r in json["result"]["items"].Cast()) + { + var descriptions = new List(); + if (!string.IsNullOrWhiteSpace((string)r["group_name"])) + descriptions.Add("Group Name: " + (string)r["group_name"]); + var link = new Uri((string)r["download"]); + var details = new Uri($"{SiteLink}torrents.php?id={(string)r["group_id"]}"); + var publishDate = DateTime.ParseExact((string)r["rls_utc"] + " +00:00", "yyyy-MM-dd HH:mm:ss zzz", CultureInfo.InvariantCulture); + var tags = string.Join(",", r["tags"]); + char[] delimiters = { ',', ' ', '/', ')', '(', '.', ';', '[', ']', '"', '|', ':' }; + var releaseGenres = ValidList.Intersect(tags.ToLower().Split(delimiters, System.StringSplitOptions.RemoveEmptyEntries)).ToList(); + descriptions.Add("Tags: " + string.Join(",", releaseGenres)); + var releaseCats = ValidCats.Intersect(tags.ToLower().Split(delimiters, System.StringSplitOptions.RemoveEmptyEntries)).ToList(); + var release = new ReleaseInfo + { + Title = (string)r["rls_name"], + Category = MapTrackerCatToNewznab(releaseCats.Any() ? releaseCats.First() : "TV"), + Details = details, + Guid = link, + Link = link, + PublishDate = publishDate, + Seeders = (int)r["seed"], + Peers = (int)r["seed"] + (int)r["leech"], + Size = (long)r["size"], + Grabs = (int)r["snatch"], + UploadVolumeFactor = 1, + DownloadVolumeFactor = 0, // ratioless + MinimumRatio = 0, // ratioless + MinimumSeedTime = 86400, // 24 hours + Description = string.Join("
\n", descriptions) + }; + if (release.Genres == null) + release.Genres = new List(); + release.Genres = releaseGenres; + if (!string.IsNullOrEmpty((string)r["series_banner"])) + release.Poster = new Uri((string)r["series_banner"]); + + releases.Add(release); + } + } + catch (Exception ex) + { + OnParseError(response.ContentString, ex); + } + return releases; + } + } +}