From edc09fd213e9a2cc8ccdb7e1ebcc1b489e2ed228 Mon Sep 17 00:00:00 2001 From: Dmitry Chepurovskiy Date: Sat, 17 Oct 2020 01:14:10 +0300 Subject: [PATCH] Anilibria: add Public Russian Anime site resolves #5762 (#9836) --- README.md | 1 + src/Jackett.Common/Indexers/AniLibria.cs | 135 ++++++++++++++++++ .../Bespoke/ConfigurationDataAniLibria.cs | 22 +++ 3 files changed, 158 insertions(+) create mode 100644 src/Jackett.Common/Indexers/AniLibria.cs create mode 100644 src/Jackett.Common/Models/IndexerConfig/Bespoke/ConfigurationDataAniLibria.cs diff --git a/README.md b/README.md index de3a871a7..4045f82d2 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/ * ACG.RIP * ACGsou (36DM) * Anidex + * AniLibria * Anime Tosho * AniRena * AniSource diff --git a/src/Jackett.Common/Indexers/AniLibria.cs b/src/Jackett.Common/Indexers/AniLibria.cs new file mode 100644 index 000000000..7eb65e00a --- /dev/null +++ b/src/Jackett.Common/Indexers/AniLibria.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using AngleSharp.Dom; +using AngleSharp.Html.Parser; +using AngleSharp.Html.Dom; +using Jackett.Common.Models; +using Jackett.Common.Models.IndexerConfig.Bespoke; +using Jackett.Common.Services.Interfaces; +using Jackett.Common.Utils; +using Jackett.Common.Utils.Clients; +using Newtonsoft.Json.Linq; +using NLog; +using System.Text.RegularExpressions; + +namespace Jackett.Common.Indexers +{ + [ExcludeFromCodeCoverage] + public class AniLibria : BaseWebIndexer + { + public AniLibria(IIndexerConfigurationService configService, Utils.Clients.WebClient wc, Logger l, IProtectionService ps) + : base(id: "AniLibria", + name: "AniLibria", + description: "AniLibria is a Public torrent tracker for anime, voiced on russian by AniLibria team", + link: "https://www.anilibria.tv/", + caps: new TorznabCapabilities(), + configService: configService, + client: wc, + logger: l, + p: ps, + configData: new ConfigurationDataAniLibria()) + { + Encoding = Encoding.UTF8; + Language = "ru-ru"; + Type = "public"; + + // Configure the category mappings + AddCategoryMapping(1, TorznabCatType.TVAnime, "Anime"); + } + private ConfigurationDataAniLibria Configuration + { + get => (ConfigurationDataAniLibria)configData; + set => configData = value; + } + + public override async Task ApplyConfiguration(JToken configJson) + { + LoadValuesFromJson(configJson); + var releases = await PerformQuery(new TorznabQuery()); + + await ConfigureIfOK(string.Empty, releases.Any(), () => + throw new Exception("Could not find releases from this URL")); + + return IndexerConfigurationStatus.Completed; + } + + // If the search string is empty use the latest releases + protected override async Task> PerformQuery(TorznabQuery query) + => query.IsTest || string.IsNullOrWhiteSpace(query.SearchTerm) + ? await FetchNewReleases() + : await PerformSearch(query); + + private async Task> PerformSearch(TorznabQuery query) + { + var queryParameters = new NameValueCollection + { + { "search", query.SearchTerm }, + { "filter", "names,poster.url,code,torrents.list,season.year" }, + }; + var response = await RequestWithCookiesAndRetryAsync(Configuration.ApiLink.Value + "/searchTitles?" + queryParameters.GetQueryString()); + if (response.Status != System.Net.HttpStatusCode.OK) + throw new WebException($"AniLibria search returned unexpected result. Expected 200 OK but got {response.Status}.", WebExceptionStatus.ProtocolError); + + var results = ParseApiResults(response.ContentString); + return results.Where(release => query.MatchQueryStringAND(release.Title)); + } + + private async Task> FetchNewReleases() + { + var queryParameters = new NameValueCollection + { + { "limit", "100" }, + { "filter", "names,poster.url,code,torrents.list,season.year" }, + }; + var response = await RequestWithCookiesAndRetryAsync(Configuration.ApiLink.Value + "/getUpdates?" + queryParameters.GetQueryString()); + if (response.Status != System.Net.HttpStatusCode.OK) + throw new WebException($"AniLibria search returned unexpected result. Expected 200 OK but got {response.Status}.", WebExceptionStatus.ProtocolError); + + return ParseApiResults(response.ContentString); + } + + private string composeTitle(dynamic json) { + var title = json.names.ru; + title += " / " + json.names.en; + if (json.alternative is string) + title += " / " + json.names.alternative; + title += " " + json.season.year; + return title; + } + + private List ParseApiResults(string json) + { + var releases = new List(); + foreach (dynamic r in JArray.Parse(json)) { + var baseRelease = new ReleaseInfo(); + baseRelease.Title = composeTitle(r); + baseRelease.BannerUrl = new Uri(Configuration.StaticLink.Value + r.poster.url); + baseRelease.Comments = new Uri(SiteLink + "/release/" + r.code + ".html"); + baseRelease.DownloadVolumeFactor = 0; + baseRelease.UploadVolumeFactor = 1; + baseRelease.Category = new int[]{ TorznabCatType.TVAnime.ID }; + foreach (var t in r.torrents.list) { + var release = (ReleaseInfo)baseRelease.Clone(); + release.Title += " [" + t.quality["string"] + "] - " + t.series["string"]; + release.Size = t.total_size; + release.Seeders = t.seeders; + release.Peers = t.leechers + t.seeders; + release.Grabs = t.downloads; + release.Link = new Uri(SiteLink + t.url); + release.PublishDate = new System.DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc).AddSeconds(Convert.ToDouble(t.uploaded_timestamp)).ToLocalTime(); + releases.Add(release); + } + } + + return releases; + } + } +} diff --git a/src/Jackett.Common/Models/IndexerConfig/Bespoke/ConfigurationDataAniLibria.cs b/src/Jackett.Common/Models/IndexerConfig/Bespoke/ConfigurationDataAniLibria.cs new file mode 100644 index 000000000..72da4b80c --- /dev/null +++ b/src/Jackett.Common/Models/IndexerConfig/Bespoke/ConfigurationDataAniLibria.cs @@ -0,0 +1,22 @@ +namespace Jackett.Common.Models.IndexerConfig.Bespoke +{ + internal class ConfigurationDataAniLibria : ConfigurationData + { + public StringItem ApiLink { get; private set; } + public StringItem StaticLink { get; private set; } + + public ConfigurationDataAniLibria() : base() + { + ApiLink = new StringItem + { + Name = "API Url", + Value = "https://api.anilibria.tv/v2/" + }; + StaticLink = new StringItem + { + Name = "Static Url", + Value = "https://static.anilibria.tv/" + }; + } + } +}