DownloadClient and MangaConnector improvements

DownloadClient is now abstract for HttpDownloadClient and ChromiumDownloadClient
The chromium client will exit the headless browser (on clean exit of the program).
The field "name" of MangaConnector is no longer abstract, instead set through constructor.
This commit is contained in:
glax 2023-09-08 23:27:09 +02:00
parent 017701867d
commit 569622099d
9 changed files with 279 additions and 238 deletions

View File

@ -0,0 +1,95 @@
using System.Net;
using System.Text;
using HtmlAgilityPack;
using PuppeteerSharp;
namespace Tranga.MangaConnectors;
internal class ChromiumDownloadClient : DownloadClient
{
private IBrowser browser { get; set; }
private const string ChromiumVersion = "1154303";
private async Task<IBrowser> DownloadBrowser()
{
BrowserFetcher browserFetcher = new BrowserFetcher();
foreach(string rev in browserFetcher.LocalRevisions().Where(rev => rev != ChromiumVersion))
browserFetcher.Remove(rev);
if (!browserFetcher.LocalRevisions().Contains(ChromiumVersion))
{
Log("Downloading headless browser");
DateTime last = DateTime.Now.Subtract(TimeSpan.FromSeconds(5));
browserFetcher.DownloadProgressChanged += (_, args) =>
{
double currentBytes = Convert.ToDouble(args.BytesReceived) / Convert.ToDouble(args.TotalBytesToReceive);
if (args.TotalBytesToReceive == args.BytesReceived)
Log("Browser downloaded.");
else if (DateTime.Now > last.AddSeconds(1))
{
Log($"Browser download progress: {currentBytes:P2}");
last = DateTime.Now;
}
};
if (!browserFetcher.CanDownloadAsync(ChromiumVersion).Result)
{
Log($"Can't download browser version {ChromiumVersion}");
throw new Exception();
}
await browserFetcher.DownloadAsync(ChromiumVersion);
}
Log("Starting Browser.");
return await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
ExecutablePath = browserFetcher.GetExecutablePath(ChromiumVersion),
Args = new [] {
"--disable-gpu",
"--disable-dev-shm-usage",
"--disable-setuid-sandbox",
"--no-sandbox"}
});
}
public ChromiumDownloadClient(GlobalBase clone, Dictionary<byte, int> rateLimitRequestsPerMinute) : base(clone, rateLimitRequestsPerMinute)
{
this.browser = DownloadBrowser().Result;
}
protected override RequestResult MakeRequestInternal(string url, string? referrer = null)
{
IPage page = this.browser!.NewPageAsync().Result;
IResponse response = page.GoToAsync(url, WaitUntilNavigation.DOMContentLoaded).Result;
Stream stream = Stream.Null;
HtmlDocument? document = null;
if (response.Headers.TryGetValue("Content-Type", out string? content))
{
if (content.Contains("text/html"))
{
string htmlString = page.GetContentAsync().Result;
stream = new MemoryStream(Encoding.Default.GetBytes(htmlString));
document = new ();
document.LoadHtml(htmlString);
}else if (content.Contains("image"))
{
stream = new MemoryStream(response.BufferAsync().Result);
}
}
else
{
page.CloseAsync();
return new RequestResult(HttpStatusCode.InternalServerError, null, Stream.Null);
}
page.CloseAsync();
return new RequestResult(response.Status, document, stream, false, "");
}
public override void Close()
{
this.browser.CloseAsync();
}
}

View File

@ -1,107 +1,66 @@
using System.Net; using System.Net;
using System.Net.Http.Headers; using HtmlAgilityPack;
namespace Tranga.MangaConnectors; namespace Tranga.MangaConnectors;
internal class DownloadClient : GlobalBase internal abstract class DownloadClient : GlobalBase
{
private readonly Dictionary<byte, DateTime> _lastExecutedRateLimit;
private readonly Dictionary<byte, TimeSpan> _rateLimit;
protected DownloadClient(GlobalBase clone, Dictionary<byte, int> rateLimitRequestsPerMinute) : base(clone)
{ {
private static readonly HttpClient Client = new() this._lastExecutedRateLimit = new();
_rateLimit = new();
foreach (KeyValuePair<byte, int> limit in rateLimitRequestsPerMinute)
_rateLimit.Add(limit.Key, TimeSpan.FromMinutes(1).Divide(limit.Value));
}
public RequestResult MakeRequest(string url, byte requestType, string? referrer = null)
{
if (_rateLimit.TryGetValue(requestType, out TimeSpan value))
_lastExecutedRateLimit.TryAdd(requestType, DateTime.Now.Subtract(value));
else
{ {
Timeout = TimeSpan.FromSeconds(60), Log("RequestType not configured for rate-limit.");
DefaultRequestHeaders = return new RequestResult(HttpStatusCode.NotAcceptable, null, Stream.Null);
{
UserAgent =
{
new ProductInfoHeaderValue("Tranga", "0.1")
}
}
};
private readonly Dictionary<byte, DateTime> _lastExecutedRateLimit;
private readonly Dictionary<byte, TimeSpan> _rateLimit;
public DownloadClient(GlobalBase clone, Dictionary<byte, int> rateLimitRequestsPerMinute) : base(clone)
{
_lastExecutedRateLimit = new();
_rateLimit = new();
foreach(KeyValuePair<byte, int> limit in rateLimitRequestsPerMinute)
_rateLimit.Add(limit.Key, TimeSpan.FromMinutes(1).Divide(limit.Value));
} }
/// <summary> TimeSpan rateLimitTimeout = _rateLimit[requestType]
/// Request Webpage .Subtract(DateTime.Now.Subtract(_lastExecutedRateLimit[requestType]));
/// </summary>
/// <param name="url"></param> if (rateLimitTimeout > TimeSpan.Zero)
/// <param name="requestType">For RateLimits: Same Endpoints use same type</param> Thread.Sleep(rateLimitTimeout);
/// <param name="referrer">Used in http request header</param>
/// <returns>RequestResult with StatusCode and Stream of received data</returns> RequestResult result = MakeRequestInternal(url, referrer);
public RequestResult MakeRequest(string url, byte requestType, string? referrer = null) _lastExecutedRateLimit[requestType] = DateTime.Now;
return result;
}
protected abstract RequestResult MakeRequestInternal(string url, string? referrer = null);
public abstract void Close();
public struct RequestResult
{
public HttpStatusCode statusCode { get; }
public Stream result { get; }
public bool hasBeenRedirected { get; }
public string? redirectedToUrl { get; }
public HtmlDocument? htmlDocument { get; }
public RequestResult(HttpStatusCode statusCode, HtmlDocument? htmlDocument, Stream result)
{ {
if (_rateLimit.TryGetValue(requestType, out TimeSpan value)) this.statusCode = statusCode;
_lastExecutedRateLimit.TryAdd(requestType, DateTime.Now.Subtract(value)); this.htmlDocument = htmlDocument;
else this.result = result;
{
Log("RequestType not configured for rate-limit.");
return new RequestResult(HttpStatusCode.NotAcceptable, Stream.Null);
}
TimeSpan rateLimitTimeout = _rateLimit[requestType]
.Subtract(DateTime.Now.Subtract(_lastExecutedRateLimit[requestType]));
if(rateLimitTimeout > TimeSpan.Zero)
Thread.Sleep(rateLimitTimeout);
HttpResponseMessage? response = null;
while (response is null)
{
try
{
HttpRequestMessage requestMessage = new(HttpMethod.Get, url);
if(referrer is not null)
requestMessage.Headers.Referrer = new Uri(referrer);
_lastExecutedRateLimit[requestType] = DateTime.Now;
//Log($"Requesting {requestType} {url}");
response = Client.Send(requestMessage);
}
catch (HttpRequestException e)
{
Log("Exception:\n\t{0}\n\tWaiting {1} before retrying.", e.Message, _rateLimit[requestType] * 2);
Thread.Sleep(_rateLimit[requestType] * 2);
}
}
if (!response.IsSuccessStatusCode)
{
Log($"Request-Error {response.StatusCode}: {response.ReasonPhrase}");
return new RequestResult(response.StatusCode, Stream.Null);
}
// Request has been redirected to another page. For example, it redirects directly to the results when there is only 1 result
if(response.RequestMessage is not null && response.RequestMessage.RequestUri is not null)
{
return new RequestResult(response.StatusCode, response.Content.ReadAsStream(), true, response.RequestMessage.RequestUri.AbsoluteUri);
}
return new RequestResult(response.StatusCode, response.Content.ReadAsStream());
} }
public struct RequestResult public RequestResult(HttpStatusCode statusCode, HtmlDocument? htmlDocument, Stream result, bool hasBeenRedirected, string redirectedTo)
: this(statusCode, htmlDocument, result)
{ {
public HttpStatusCode statusCode { get; } this.hasBeenRedirected = hasBeenRedirected;
public Stream result { get; } redirectedToUrl = redirectedTo;
public bool hasBeenRedirected { get; }
public string? redirectedToUrl { get; }
public RequestResult(HttpStatusCode statusCode, Stream result)
{
this.statusCode = statusCode;
this.result = result;
}
public RequestResult(HttpStatusCode statusCode, Stream result, bool hasBeenRedirected, string redirectedTo)
: this(statusCode, result)
{
this.hasBeenRedirected = hasBeenRedirected;
redirectedToUrl = redirectedTo;
}
} }
} }
}

View File

@ -0,0 +1,70 @@
using System.Net.Http.Headers;
using HtmlAgilityPack;
namespace Tranga.MangaConnectors;
internal class HttpDownloadClient : DownloadClient
{
private static readonly HttpClient Client = new()
{
Timeout = TimeSpan.FromSeconds(60),
DefaultRequestHeaders =
{
UserAgent =
{
new ProductInfoHeaderValue("Tranga", "0.1")
}
}
};
public HttpDownloadClient(GlobalBase clone, Dictionary<byte, int> rateLimitRequestsPerMinute) : base(clone, rateLimitRequestsPerMinute)
{
}
protected override RequestResult MakeRequestInternal(string url, string? referrer = null)
{
HttpResponseMessage? response = null;
while (response is null)
{
HttpRequestMessage requestMessage = new(HttpMethod.Get, url);
if (referrer is not null)
requestMessage.Headers.Referrer = new Uri(referrer);
//Log($"Requesting {requestType} {url}");
response = Client.Send(requestMessage);
}
if (!response.IsSuccessStatusCode)
{
Log($"Request-Error {response.StatusCode}: {response.ReasonPhrase}");
return new RequestResult(response.StatusCode, null, Stream.Null);
}
Stream stream = response.Content.ReadAsStream();
HtmlDocument? document = null;
if (response.Content.Headers.ContentType?.MediaType == "text/html")
{
StreamReader reader = new (stream);
document = new ();
document.LoadHtml(reader.ReadToEnd());
stream.Position = 0;
}
// Request has been redirected to another page. For example, it redirects directly to the results when there is only 1 result
if (response.RequestMessage is not null && response.RequestMessage.RequestUri is not null)
{
return new RequestResult(response.StatusCode, document, stream, true,
response.RequestMessage.RequestUri.AbsoluteUri);
}
return new RequestResult(response.StatusCode, document, stream);
}
public override void Close()
{
Log("Closing.");
}
}

View File

@ -1,5 +1,4 @@
using System.Globalization; using System.IO.Compression;
using System.IO.Compression;
using System.Net; using System.Net;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@ -16,12 +15,18 @@ public abstract class MangaConnector : GlobalBase
{ {
internal DownloadClient downloadClient { get; init; } = null!; internal DownloadClient downloadClient { get; init; } = null!;
protected MangaConnector(GlobalBase clone) : base(clone) public void StopDownloadClient()
{ {
downloadClient.Close();
}
protected MangaConnector(GlobalBase clone, string name) : base(clone)
{
this.name = name;
Directory.CreateDirectory(settings.coverImageCache); Directory.CreateDirectory(settings.coverImageCache);
} }
public abstract string name { get; } //Name of the Connector (e.g. Website) public string name { get; } //Name of the Connector (e.g. Website)
/// <summary> /// <summary>
/// Returns all Publications with the given string. /// Returns all Publications with the given string.

View File

@ -8,8 +8,6 @@ using JsonSerializer = System.Text.Json.JsonSerializer;
namespace Tranga.MangaConnectors; namespace Tranga.MangaConnectors;
public class MangaDex : MangaConnector public class MangaDex : MangaConnector
{ {
public override string name { get; }
private enum RequestType : byte private enum RequestType : byte
{ {
Manga, Manga,
@ -19,10 +17,9 @@ public class MangaDex : MangaConnector
Author, Author,
} }
public MangaDex(GlobalBase clone) : base(clone) public MangaDex(GlobalBase clone) : base(clone, "MangaDex")
{ {
name = "MangaDex"; this.downloadClient = new HttpDownloadClient(clone, new Dictionary<byte, int>()
this.downloadClient = new DownloadClient(clone, new Dictionary<byte, int>()
{ {
{(byte)RequestType.Manga, 250}, {(byte)RequestType.Manga, 250},
{(byte)RequestType.Feed, 250}, {(byte)RequestType.Feed, 250},

View File

@ -8,12 +8,9 @@ namespace Tranga.MangaConnectors;
public class MangaKatana : MangaConnector public class MangaKatana : MangaConnector
{ {
public override string name { get; } public MangaKatana(GlobalBase clone) : base(clone, "MangaKatana")
public MangaKatana(GlobalBase clone) : base(clone)
{ {
this.name = "MangaKatana"; this.downloadClient = new HttpDownloadClient(clone, new Dictionary<byte, int>()
this.downloadClient = new DownloadClient(clone, new Dictionary<byte, int>()
{ {
{1, 60} {1, 60}
}); });

View File

@ -8,12 +8,9 @@ namespace Tranga.MangaConnectors;
public class Manganato : MangaConnector public class Manganato : MangaConnector
{ {
public override string name { get; } public Manganato(GlobalBase clone) : base(clone, "Manganato")
public Manganato(GlobalBase clone) : base(clone)
{ {
this.name = "Manganato"; this.downloadClient = new HttpDownloadClient(clone, new Dictionary<byte, int>()
this.downloadClient = new DownloadClient(clone, new Dictionary<byte, int>()
{ {
{1, 60} {1, 60}
}); });
@ -22,24 +19,22 @@ public class Manganato : MangaConnector
public override Manga[] GetManga(string publicationTitle = "") public override Manga[] GetManga(string publicationTitle = "")
{ {
Log($"Searching Publications. Term=\"{publicationTitle}\""); Log($"Searching Publications. Term=\"{publicationTitle}\"");
string sanitizedTitle = string.Join('_', Regex.Matches(publicationTitle, "[A-z]*")).ToLower(); string sanitizedTitle = string.Join('_', Regex.Matches(publicationTitle, "[A-z]*").Where(str => str.Length > 0)).ToLower();
string requestUrl = $"https://manganato.com/search/story/{sanitizedTitle}"; string requestUrl = $"https://manganato.com/search/story/{sanitizedTitle}";
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest(requestUrl, 1); downloadClient.MakeRequest(requestUrl, 1);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return Array.Empty<Manga>(); return Array.Empty<Manga>();
Manga[] publications = ParsePublicationsFromHtml(requestResult.result); if (requestResult.htmlDocument is null)
return Array.Empty<Manga>();
Manga[] publications = ParsePublicationsFromHtml(requestResult.htmlDocument);
Log($"Retrieved {publications.Length} publications. Term=\"{publicationTitle}\""); Log($"Retrieved {publications.Length} publications. Term=\"{publicationTitle}\"");
return publications; return publications;
} }
private Manga[] ParsePublicationsFromHtml(Stream html) private Manga[] ParsePublicationsFromHtml(HtmlDocument document)
{ {
StreamReader reader = new (html);
string htmlString = reader.ReadToEnd();
HtmlDocument document = new ();
document.LoadHtml(htmlString);
IEnumerable<HtmlNode> searchResults = document.DocumentNode.Descendants("div").Where(n => n.HasClass("search-story-item")); IEnumerable<HtmlNode> searchResults = document.DocumentNode.Descendants("div").Where(n => n.HasClass("search-story-item"));
List<string> urls = new(); List<string> urls = new();
foreach (HtmlNode mangaResult in searchResults) foreach (HtmlNode mangaResult in searchResults)
@ -65,16 +60,14 @@ public class Manganato : MangaConnector
downloadClient.MakeRequest(url, 1); downloadClient.MakeRequest(url, 1);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return null; return null;
return ParseSinglePublicationFromHtml(requestResult.result, url.Split('/')[^1]); if (requestResult.htmlDocument is null)
return null;
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, url.Split('/')[^1]);
} }
private Manga ParseSinglePublicationFromHtml(Stream html, string publicationId) private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId)
{ {
StreamReader reader = new (html);
string htmlString = reader.ReadToEnd();
HtmlDocument document = new ();
document.LoadHtml(htmlString);
string status = ""; string status = "";
Dictionary<string, string> altTitles = new(); Dictionary<string, string> altTitles = new();
Dictionary<string, string>? links = null; Dictionary<string, string>? links = null;
@ -144,17 +137,15 @@ public class Manganato : MangaConnector
return Array.Empty<Chapter>(); return Array.Empty<Chapter>();
//Return Chapters ordered by Chapter-Number //Return Chapters ordered by Chapter-Number
List<Chapter> chapters = ParseChaptersFromHtml(manga, requestResult.result); if (requestResult.htmlDocument is null)
return Array.Empty<Chapter>();
List<Chapter> chapters = ParseChaptersFromHtml(manga, requestResult.htmlDocument);
Log($"Got {chapters.Count} chapters. {manga}"); Log($"Got {chapters.Count} chapters. {manga}");
return chapters.OrderBy(chapter => Convert.ToSingle(chapter.chapterNumber, numberFormatDecimalPoint)).ToArray(); return chapters.OrderBy(chapter => Convert.ToSingle(chapter.chapterNumber, numberFormatDecimalPoint)).ToArray();
} }
private List<Chapter> ParseChaptersFromHtml(Manga manga, Stream html) private List<Chapter> ParseChaptersFromHtml(Manga manga, HtmlDocument document)
{ {
StreamReader reader = new (html);
string htmlString = reader.ReadToEnd();
HtmlDocument document = new ();
document.LoadHtml(htmlString);
List<Chapter> ret = new(); List<Chapter> ret = new();
HtmlNode chapterList = document.DocumentNode.Descendants("ul").First(l => l.HasClass("row-content-chapter")); HtmlNode chapterList = document.DocumentNode.Descendants("ul").First(l => l.HasClass("row-content-chapter"));
@ -186,7 +177,7 @@ public class Manganato : MangaConnector
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return requestResult.statusCode; return requestResult.statusCode;
string[] imageUrls = ParseImageUrlsFromHtml(requestResult.result); string[] imageUrls = ParseImageUrlsFromHtml(requestResult.htmlDocument);
string comicInfoPath = Path.GetTempFileName(); string comicInfoPath = Path.GetTempFileName();
File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString());
@ -194,12 +185,8 @@ public class Manganato : MangaConnector
return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, "https://chapmanganato.com/", progressToken:progressToken); return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, "https://chapmanganato.com/", progressToken:progressToken);
} }
private string[] ParseImageUrlsFromHtml(Stream html) private string[] ParseImageUrlsFromHtml(HtmlDocument document)
{ {
StreamReader reader = new (html);
string htmlString = reader.ReadToEnd();
HtmlDocument document = new ();
document.LoadHtml(htmlString);
List<string> ret = new(); List<string> ret = new();
HtmlNode imageContainer = HtmlNode imageContainer =

View File

@ -1,72 +1,20 @@
using System.Globalization; using System.Net;
using System.Net;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Xml.Linq; using System.Xml.Linq;
using HtmlAgilityPack; using HtmlAgilityPack;
using Newtonsoft.Json; using Newtonsoft.Json;
using PuppeteerSharp;
using Tranga.Jobs; using Tranga.Jobs;
namespace Tranga.MangaConnectors; namespace Tranga.MangaConnectors;
public class Mangasee : MangaConnector public class Mangasee : MangaConnector
{ {
public override string name { get; } public Mangasee(GlobalBase clone) : base(clone, "Mangasee")
private IBrowser? _browser;
private const string ChromiumVersion = "1154303";
public Mangasee(GlobalBase clone) : base(clone)
{ {
this.name = "Mangasee"; this.downloadClient = new ChromiumDownloadClient(clone, new Dictionary<byte, int>()
this.downloadClient = new DownloadClient(clone, new Dictionary<byte, int>()
{ {
{ 1, 60 } { 1, 60 }
}); });
Task d = new Task(DownloadBrowser);
d.Start();
}
private async void DownloadBrowser()
{
BrowserFetcher browserFetcher = new BrowserFetcher();
foreach(string rev in browserFetcher.LocalRevisions().Where(rev => rev != ChromiumVersion))
browserFetcher.Remove(rev);
if (!browserFetcher.LocalRevisions().Contains(ChromiumVersion))
{
Log("Downloading headless browser");
DateTime last = DateTime.Now.Subtract(TimeSpan.FromSeconds(5));
browserFetcher.DownloadProgressChanged += (_, args) =>
{
double currentBytes = Convert.ToDouble(args.BytesReceived) / Convert.ToDouble(args.TotalBytesToReceive);
if (args.TotalBytesToReceive == args.BytesReceived)
Log("Browser downloaded.");
else if (DateTime.Now > last.AddSeconds(1))
{
Log($"Browser download progress: {currentBytes:P2}");
last = DateTime.Now;
}
};
if (!browserFetcher.CanDownloadAsync(ChromiumVersion).Result)
{
Log($"Can't download browser version {ChromiumVersion}");
throw new Exception();
}
await browserFetcher.DownloadAsync(ChromiumVersion);
}
Log("Starting Browser.");
this._browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
ExecutablePath = browserFetcher.GetExecutablePath(ChromiumVersion),
Args = new [] {
"--disable-gpu",
"--disable-dev-shm-usage",
"--disable-setuid-sandbox",
"--no-sandbox"}
});
} }
public override Manga[] GetManga(string publicationTitle = "") public override Manga[] GetManga(string publicationTitle = "")
@ -78,38 +26,27 @@ public class Mangasee : MangaConnector
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return Array.Empty<Manga>(); return Array.Empty<Manga>();
Manga[] publications = ParsePublicationsFromHtml(requestResult.result, publicationTitle); if (requestResult.htmlDocument is null)
return Array.Empty<Manga>();
Manga[] publications = ParsePublicationsFromHtml(requestResult.htmlDocument, publicationTitle);
Log($"Retrieved {publications.Length} publications. Term=\"{publicationTitle}\""); Log($"Retrieved {publications.Length} publications. Term=\"{publicationTitle}\"");
return publications; return publications;
} }
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromUrl(string url)
{ {
while (this._browser is null)
{
Log("Waiting for headless browser to download...");
Thread.Sleep(1000);
}
Regex publicationIdRex = new(@"https:\/\/mangasee123.com\/manga\/(.*)(\/.*)*"); Regex publicationIdRex = new(@"https:\/\/mangasee123.com\/manga\/(.*)(\/.*)*");
string publicationId = publicationIdRex.Match(url).Groups[1].Value; string publicationId = publicationIdRex.Match(url).Groups[1].Value;
IPage page = _browser!.NewPageAsync().Result;
IResponse response = page.GoToAsync(url, WaitUntilNavigation.DOMContentLoaded).Result;
if (response.Ok)
{
HtmlDocument document = new();
document.LoadHtml(page.GetContentAsync().Result);
page.CloseAsync();
return ParseSinglePublicationFromHtml(document, publicationId);
}
page.CloseAsync(); DownloadClient.RequestResult requestResult = this.downloadClient.MakeRequest(url, 1);
if(requestResult.htmlDocument is not null)
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, publicationId);
return null; return null;
} }
private Manga[] ParsePublicationsFromHtml(Stream html, string publicationTitle) private Manga[] ParsePublicationsFromHtml(HtmlDocument document, string publicationTitle)
{ {
string jsonString = new StreamReader(html).ReadToEnd(); string jsonString = document.DocumentNode.SelectSingleNode("//body").InnerText;
List<SearchResultItem> result = JsonConvert.DeserializeObject<List<SearchResultItem>>(jsonString)!; List<SearchResultItem> result = JsonConvert.DeserializeObject<List<SearchResultItem>>(jsonString)!;
Dictionary<SearchResultItem, int> queryFiltered = new(); Dictionary<SearchResultItem, int> queryFiltered = new();
foreach (SearchResultItem resultItem in result) foreach (SearchResultItem resultItem in result)
@ -244,36 +181,25 @@ public class Mangasee : MangaConnector
if (progressToken?.cancellationRequested ?? false) if (progressToken?.cancellationRequested ?? false)
return HttpStatusCode.RequestTimeout; return HttpStatusCode.RequestTimeout;
Manga chapterParentManga = chapter.parentManga; Manga chapterParentManga = chapter.parentManga;
while (this._browser is null && !(progressToken?.cancellationRequested??false))
{
Log("Waiting for headless browser to download...");
Thread.Sleep(1000);
}
if (progressToken?.cancellationRequested??false) if (progressToken?.cancellationRequested??false)
return HttpStatusCode.RequestTimeout; return HttpStatusCode.RequestTimeout;
Log($"Retrieving chapter-info {chapter} {chapterParentManga}"); Log($"Retrieving chapter-info {chapter} {chapterParentManga}");
IPage page = _browser!.NewPageAsync().Result;
IResponse response = page.GoToAsync(chapter.url).Result;
if (response.Ok)
{
HtmlDocument document = new ();
document.LoadHtml(page.GetContentAsync().Result);
page.CloseAsync();
HtmlNode gallery = document.DocumentNode.Descendants("div").First(div => div.HasClass("ImageGallery")); DownloadClient.RequestResult requestResult = this.downloadClient.MakeRequest(chapter.url, 1);
HtmlNode[] images = gallery.Descendants("img").Where(img => img.HasClass("img-fluid")).ToArray(); if(requestResult.htmlDocument is null)
List<string> urls = new(); return HttpStatusCode.RequestTimeout;
foreach(HtmlNode galleryImage in images) HtmlDocument document = requestResult.htmlDocument;
urls.Add(galleryImage.GetAttributeValue("src", ""));
string comicInfoPath = Path.GetTempFileName();
File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString());
return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, progressToken:progressToken); HtmlNode gallery = document.DocumentNode.Descendants("div").First(div => div.HasClass("ImageGallery"));
} HtmlNode[] images = gallery.Descendants("img").Where(img => img.HasClass("img-fluid")).ToArray();
List<string> urls = new();
page.CloseAsync(); foreach(HtmlNode galleryImage in images)
return response.Status; urls.Add(galleryImage.GetAttributeValue("src", ""));
string comicInfoPath = Path.GetTempFileName();
File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString());
return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, progressToken:progressToken);
} }
} }

View File

@ -67,6 +67,11 @@ public partial class Tranga : GlobalBase
jobBoss.CheckJobs(); jobBoss.CheckJobs();
Thread.Sleep(100); Thread.Sleep(100);
} }
foreach (MangaConnector connector in _connectors)
{
}
}); });
t.Start(); t.Start();
} }