diff --git a/src/Jackett.Common/Indexers/ShizaProject.cs b/src/Jackett.Common/Indexers/ShizaProject.cs
index b9459de30..2e072182f 100644
--- a/src/Jackett.Common/Indexers/ShizaProject.cs
+++ b/src/Jackett.Common/Indexers/ShizaProject.cs
@@ -1,19 +1,16 @@
using System;
using System.Collections.Generic;
-using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using System.Text;
using System.Threading.Tasks;
-using AngleSharp.Dom;
-using AngleSharp.Html.Parser;
using Jackett.Common.Models;
using Jackett.Common.Models.IndexerConfig;
using Jackett.Common.Services.Interfaces;
-using Jackett.Common.Utils;
using Jackett.Common.Utils.Clients;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
-
namespace Jackett.Common.Indexers
{
[ExcludeFromCodeCoverage]
@@ -41,11 +38,11 @@ namespace Jackett.Common.Indexers
logger: l,
p: ps,
cacheService: cs,
- configData: new ConfigurationDataBasicLoginWithEmail())
+ configData: new ConfigurationData())
{
Encoding = Encoding.UTF8;
Language = "ru-ru";
- Type = "semi-private";
+ Type = "public";
AddCategoryMapping(1, TorznabCatType.TVAnime, "Anime");
}
@@ -53,168 +50,222 @@ namespace Jackett.Common.Indexers
private ConfigurationDataBasicLoginWithEmail Configuration => (ConfigurationDataBasicLoginWithEmail)configData;
///
- /// http://shiza-project.com/accounts/login
+ /// http://shiza-project.com/graphql
///
- private string LoginUrl => SiteLink + "accounts/login";
-
- ///
- /// http://shiza-project.com/releases/search
- ///
- private string SearchUrl => SiteLink + "releases/search";
+ private string GraphqlEndpointUrl => SiteLink + "graphql";
public override async Task ApplyConfiguration(JToken configJson)
{
LoadValuesFromJson(configJson);
+ var releases = await PerformQuery(new TorznabQuery());
- var data = new Dictionary
- {
- { "field-email", Configuration.Email.Value },
- { "field-password", Configuration.Password.Value }
- };
-
- var result = await RequestLoginAndFollowRedirect(
- LoginUrl,
- data,
- null,
- returnCookiesFromFirstCall: true
- );
-
- var parser = new HtmlParser();
- var document = await parser.ParseDocumentAsync(result.ContentString);
-
- await ConfigureIfOK(result.Cookies, IsAuthorized(result), () =>
- {
- var errorMessage = document.QuerySelector("div.alert-error").Text().Trim();
- throw new ExceptionWithConfigData(errorMessage, Configuration);
- });
+ await ConfigureIfOK(string.Empty, releases.Any(), () =>
+ throw new Exception("Could not find releases from this URL"));
return IndexerConfigurationStatus.Completed;
}
- public override async Task Download(Uri link)
- {
- await EnsureAuthorized();
- return await base.Download(link);
- }
-
- // If the search string is empty use the latest releases
protected override async Task> PerformQuery(TorznabQuery query)
{
- await EnsureAuthorized();
-
- WebResult result;
- if (query.IsTest || string.IsNullOrWhiteSpace(query.SearchTerm))
+ var releasesQuery = new
{
- result = await RequestWithCookiesAndRetryAsync(SiteLink);
- }
- else
- {
- // Prepare the search query
- var queryParameters = new NameValueCollection
+ operationName = "fetchReleases",
+ variables = new
{
- { "q", query.SearchTerm}
- };
- result = await RequestWithCookiesAndRetryAsync(SearchUrl + "?" + queryParameters.GetQueryString());
- }
-
- const string ReleaseLinksSelector = "article.grid-card > a.card-box";
+ first = 50, //Number of fetched releases (required parameter) TODO: consider adding pagination
+ query = query.SearchTerm
+ },
+ query = @"
+ query fetchReleases($first: Int, $query: String) {
+ releases(first: $first, query: $query) {
+ edges {
+ node {
+ name
+ originalName
+ alternativeNames
+ publishedAt
+ slug
+ posters {
+ preview: resize(width: 360, height: 500) {
+ url
+ }
+ }
+ torrents {
+ downloaded
+ seeders
+ leechers
+ size
+ magnetUri
+ updatedAt
+ file {
+ url
+ }
+ videoQualities
+ }
+ }
+ }
+ }
+ }"
+ };
+ var headers = new Dictionary
+ {
+ { "Content-Type", "application/json; charset=utf-8" },
+ };
+ var response = await RequestWithCookiesAndRetryAsync(GraphqlEndpointUrl, method: RequestType.POST, rawbody: Newtonsoft.Json.JsonConvert.SerializeObject(releasesQuery), headers: headers);
+ var j = JsonConvert.DeserializeObject(response.ContentString);
var releases = new List();
-
- try
+ foreach (var e in j.Data.Releases.Edges)
{
- var parser = new HtmlParser();
- var document = await parser.ParseDocumentAsync(result.ContentString);
-
- foreach (var linkNode in document.QuerySelectorAll(ReleaseLinksSelector))
- {
- var url = linkNode.GetAttribute("href");
- releases.AddRange(await FetchShowReleases(url));
- }
- }
- catch (Exception ex)
- {
- OnParseError(result.ContentString, ex);
- }
-
- return releases;
- }
-
- private async Task EnsureAuthorized()
- {
- var result = await RequestWithCookiesAndRetryAsync(SiteLink);
-
- if (!IsAuthorized(result))
- {
- await ApplyConfiguration(null);
- }
- }
-
- private async Task> FetchShowReleases(string url)
- {
- var releases = new List();
- var uri = new Uri(url);
- var result = await RequestWithCookiesAndRetryAsync(url);
-
- try
- {
- var parser = new HtmlParser();
- var document = await parser.ParseDocumentAsync(result.ContentString);
- var r = document.QuerySelector("div.release > div.wrapper-release");
-
+ var n = e.Node;
var baseRelease = new ReleaseInfo
{
- Title = composeBaseTitle(r),
- Poster = new Uri(SiteLink + r.QuerySelector("a[data-fancybox]").Attributes["href"].Value),
- Details = uri,
+ Title = composeTitle(n),
+ Poster = n.Posters[0].Preview.Url,
+ Details = new Uri(SiteLink + "releases/" + n.Slug),
DownloadVolumeFactor = 0,
UploadVolumeFactor = 1,
Category = new[] { TorznabCatType.TVAnime.ID }
};
- foreach (var t in r.QuerySelectorAll("a[data-toggle]"))
+ foreach (var t in n.Torrents)
{
var release = (ReleaseInfo)baseRelease.Clone();
- release.Title += " " + t.Text().Trim();
- var tr_id = t.Attributes["href"].Value;
- var tr = r.QuerySelector("div" + tr_id);
- release.Link = new Uri(tr.QuerySelector("a.button--success").Attributes["href"].Value);
- release.Seeders = long.Parse(tr.QuerySelector("div.torrent-counter > div:nth-of-type(1)").Text().Trim().Split(' ')[0]);
- release.Peers = release.Seeders + long.Parse(tr.QuerySelector("div.torrent-counter > div:nth-of-type(2)").Text().Trim().Split(' ')[0]);
- release.Grabs = long.Parse(tr.QuerySelector("div.torrent-counter > div:nth-of-type(3)").Text().Trim().Split(' ')[0]);
- release.PublishDate = DateTime.Parse(tr.QuerySelector("time.torrent-time").Text());
- release.Size = getReleaseSize(tr);
- release.Guid = new Uri(uri.ToString() + tr_id);
+
+ release.Title += getTitleQualities(t);
+ release.Size = t.Size;
+ release.Seeders = t.Seeders;
+ release.Peers = t.Leechers + t.Seeders;
+ release.Grabs = t.Downloaded;
+ release.Link = t.File.Url;
+ release.Guid = t.File.Url;
+ release.MagnetUri = t.MagnetUri;
+ release.PublishDate = getActualPublishDate(n, t);
releases.Add(release);
}
}
- catch (Exception ex)
- {
- OnParseError(result.ContentString, ex);
- }
return releases;
}
- private string composeBaseTitle(IElement release)
+ private string composeTitle(Node n)
{
- var titleDiv = release.QuerySelector("section:nth-of-type(2) > div.card > article:nth-of-type(1) > div.card-header");
- return titleDiv.QuerySelector("h3").Text() + " " + titleDiv.QuerySelector("p").Text();
+ var title = n.Name;
+ title += " / " + n.OriginalName;
+ foreach (string name in n.AlternativeNames)
+ title += " / " + name;
+
+ return title;
}
- // Appending id to differentiate between different quality versions
- private bool IsAuthorized(WebResult result)
+ private DateTime getActualPublishDate(Node n, Torrent t)
{
- return result.ContentString.Contains("/logout");
+ if (n.PublishedAt == null)
+ {
+ return t.UpdatedAt;
+ }
+ else
+ {
+ return (t.UpdatedAt > n.PublishedAt) ? t.UpdatedAt : n.PublishedAt.Value;
+ }
}
- private static long getReleaseSize(IElement tr)
+ private string getTitleQualities(Torrent t)
{
- var size = tr.QuerySelector("a.torrent-size").Text().Trim();
- size = size.Replace("КБ", "KB");
- size = size.Replace("МБ", "MB");
- size = size.Replace("ГБ", "GB");
- size = size.Replace("ТБ", "TB");
- return ReleaseInfo.GetBytes(size);
+ var s = " [";
+
+ foreach (string q in t.VideoQualities)
+ {
+ s += " " + q;
+ }
+
+ return s + " ]";
+ }
+
+ public partial class ReleasesResponse
+ {
+ [JsonProperty("data")]
+ public Data Data { get; set; }
+ }
+
+ public partial class Data
+ {
+ [JsonProperty("releases")]
+ public Releases Releases { get; set; }
+ }
+
+ public partial class Releases
+ {
+ [JsonProperty("edges")]
+ public Edge[] Edges { get; set; }
+ }
+
+ public partial class Edge
+ {
+ [JsonProperty("node")]
+ public Node Node { get; set; }
+ }
+
+ public partial class Node
+ {
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("originalName")]
+ public string OriginalName { get; set; }
+
+ [JsonProperty("alternativeNames")]
+ public string[] AlternativeNames { get; set; }
+
+ [JsonProperty("publishedAt")]
+ public DateTime? PublishedAt { get; set; }
+
+ [JsonProperty("slug")]
+ public string Slug { get; set; }
+
+ [JsonProperty("posters")]
+ public Poster[] Posters { get; set; }
+
+ [JsonProperty("torrents")]
+ public Torrent[] Torrents { get; set; }
+ }
+
+ public partial class Poster
+ {
+ [JsonProperty("preview")]
+ public Preview Preview { get; set; }
+ }
+
+ public partial class Preview
+ {
+ [JsonProperty("url")]
+ public Uri Url { get; set; }
+ }
+
+ public partial class Torrent
+ {
+ [JsonProperty("downloaded")]
+ public long Downloaded { get; set; }
+
+ [JsonProperty("seeders")]
+ public long Seeders { get; set; }
+
+ [JsonProperty("leechers")]
+ public long Leechers { get; set; }
+
+ [JsonProperty("size")]
+ public long Size { get; set; }
+
+ [JsonProperty("magnetUri")]
+ public Uri MagnetUri { get; set; }
+
+ [JsonProperty("updatedAt")]
+ public DateTime UpdatedAt { get; set; }
+
+ [JsonProperty("file")]
+ public Preview File { get; set; }
+
+ [JsonProperty("videoQualities")]
+ public string[] VideoQualities { get; set; }
}
}
}