From 8381951168b5e61c01708e2fa82d9b47f6007ff2 Mon Sep 17 00:00:00 2001 From: glax Date: Thu, 1 Jun 2023 13:13:53 +0200 Subject: [PATCH] #2 First Attempt --- Tranga.sln.DotSettings | 1 + Tranga/Connector.cs | 29 ++++- Tranga/Connectors/MangaDex.cs | 19 +--- Tranga/Connectors/Manganato.cs | 187 +++++++++++++++++++++++++++++++++ Tranga/TaskManager.cs | 12 ++- Tranga/Tranga.csproj | 1 + 6 files changed, 224 insertions(+), 25 deletions(-) create mode 100644 Tranga/Connectors/Manganato.cs diff --git a/Tranga.sln.DotSettings b/Tranga.sln.DotSettings index 05f49b5..4d3085b 100644 --- a/Tranga.sln.DotSettings +++ b/Tranga.sln.DotSettings @@ -1,4 +1,5 @@  True + True True True \ No newline at end of file diff --git a/Tranga/Connector.cs b/Tranga/Connector.cs index 932bfb5..24e6ad2 100644 --- a/Tranga/Connector.cs +++ b/Tranga/Connector.cs @@ -126,9 +126,9 @@ public abstract class Connector /// /// /// RequestType for Rate-Limit - private void DownloadImage(string imageUrl, string fullPath, byte requestType) + private void DownloadImage(string imageUrl, string fullPath, byte requestType, string? referrer = null) { - DownloadClient.RequestResult requestResult = downloadClient.MakeRequest(imageUrl, requestType); + DownloadClient.RequestResult requestResult = downloadClient.MakeRequest(imageUrl, requestType, referrer); byte[] buffer = new byte[requestResult.result.Length]; requestResult.result.ReadExactly(buffer, 0, buffer.Length); File.WriteAllBytes(fullPath, buffer); @@ -141,7 +141,7 @@ public abstract class Connector /// Full path to save archive to (without file ending .cbz) /// Path of the generate Chapter ComicInfo.xml, if it was generated /// RequestType for RateLimits - protected void DownloadChapterImages(string[] imageUrls, string saveArchiveFilePath, byte requestType, string? comicInfoPath = null) + protected void DownloadChapterImages(string[] imageUrls, string saveArchiveFilePath, byte requestType, string? comicInfoPath = null, string? referrer = null) { logger?.WriteLine("Connector", $"Downloading Images for {saveArchiveFilePath}"); //Check if Publication Directory already exists @@ -162,7 +162,7 @@ public abstract class Connector string[] split = imageUrl.Split('.'); string extension = split[^1]; logger?.WriteLine("Connector", $"Downloading Image {chapter + 1}/{imageUrls.Length}"); - DownloadImage(imageUrl, Path.Join(tempFolder, $"{chapter++}.{extension}"), requestType); + DownloadImage(imageUrl, Path.Join(tempFolder, $"{chapter++}.{extension}"), requestType, referrer); } if(comicInfoPath is not null) @@ -174,6 +174,23 @@ public abstract class Connector Directory.Delete(tempFolder, true); //Cleanup } + protected string SaveCoverImageToCache(string url, byte requestType) + { + string[] split = url.Split('/'); + string filename = split[^1]; + string saveImagePath = Path.Join(imageCachePath, filename); + + if (File.Exists(saveImagePath)) + return filename; + + DownloadClient.RequestResult coverResult = downloadClient.MakeRequest(url, requestType); + using MemoryStream ms = new(); + coverResult.result.CopyTo(ms); + File.WriteAllBytes(saveImagePath, ms.ToArray()); + logger?.WriteLine(this.GetType().ToString(), $"Saving image to {saveImagePath}"); + return filename; + } + protected class DownloadClient { private static readonly HttpClient Client = new(); @@ -202,7 +219,7 @@ public abstract class Connector /// /// For RateLimits: Same Endpoints use same type /// RequestResult with StatusCode and Stream of received data - public RequestResult MakeRequest(string url, byte requestType) + public RequestResult MakeRequest(string url, byte requestType, string? referrer = null) { if (_rateLimit.TryGetValue(requestType, out TimeSpan value)) _lastExecutedRateLimit.TryAdd(requestType, DateTime.Now.Subtract(value)); @@ -224,6 +241,8 @@ public abstract class Connector try { HttpRequestMessage requestMessage = new(HttpMethod.Get, url); + if(referrer is not null) + requestMessage.Headers.Referrer = new Uri(referrer); _lastExecutedRateLimit[requestType] = DateTime.Now; response = Client.Send(requestMessage); } diff --git a/Tranga/Connectors/MangaDex.cs b/Tranga/Connectors/MangaDex.cs index b3bad9b..dd2cdb4 100644 --- a/Tranga/Connectors/MangaDex.cs +++ b/Tranga/Connectors/MangaDex.cs @@ -102,7 +102,7 @@ public class MangaDex : Connector string? coverUrl = GetCoverUrl(publicationId, posterId); string? coverCacheName = null; if (coverUrl is not null) - coverCacheName = SaveImage(coverUrl); + coverCacheName = SaveCoverImageToCache(coverUrl, (byte)RequestType.AtHomeServer); string? author = GetAuthor(authorId); @@ -273,21 +273,4 @@ public class MangaDex : Connector logger?.WriteLine(this.GetType().ToString(), $"Got author {authorId} -> {author}"); return author; } - - private string SaveImage(string url) - { - string[] split = url.Split('/'); - string filename = split[^1]; - string saveImagePath = Path.Join(imageCachePath, filename); - - if (File.Exists(saveImagePath)) - return filename; - - DownloadClient.RequestResult coverResult = downloadClient.MakeRequest(url, (byte)RequestType.AtHomeServer); - using MemoryStream ms = new(); - coverResult.result.CopyTo(ms); - File.WriteAllBytes(saveImagePath, ms.ToArray()); - logger?.WriteLine(this.GetType().ToString(), $"Saving image to {saveImagePath}"); - return filename; - } } \ No newline at end of file diff --git a/Tranga/Connectors/Manganato.cs b/Tranga/Connectors/Manganato.cs new file mode 100644 index 0000000..4123ec2 --- /dev/null +++ b/Tranga/Connectors/Manganato.cs @@ -0,0 +1,187 @@ +using System.Collections; +using System.Net; +using System.Text.RegularExpressions; +using HtmlAgilityPack; +using Logging; + +namespace Tranga.Connectors; + +public class Manganato : Connector +{ + public override string name { get; } + + public Manganato(string downloadLocation, string imageCachePath, Logger? logger) : base(downloadLocation, imageCachePath, logger) + { + this.name = "Manganato"; + this.downloadClient = new DownloadClient(new Dictionary() + { + {(byte)1, 100} + }, logger); + } + + public override Publication[] GetPublications(string publicationTitle = "") + { + string sanitizedTitle = publicationTitle.ToLower().Replace(' ', '_'); + logger?.WriteLine(this.GetType().ToString(), $"Getting Publications (title={sanitizedTitle})"); + string requestUrl = $"https://manganato.com/search/story/{sanitizedTitle}"; + DownloadClient.RequestResult requestResult = + downloadClient.MakeRequest(requestUrl, (byte)1); + if (requestResult.statusCode != HttpStatusCode.OK) + return Array.Empty(); + + return ParsePublicationsFromHtml(requestResult.result); + } + + private Publication[] ParsePublicationsFromHtml(Stream html) + { + StreamReader reader = new (html); + string htmlString = reader.ReadToEnd(); + HtmlDocument document = new (); + document.LoadHtml(htmlString); + IEnumerable searchResults = document.DocumentNode.Descendants("div").Where(n => n.HasClass("search-story-item")); + List urls = new(); + foreach (HtmlNode mangaResult in searchResults) + { + urls.Add(mangaResult.Descendants("a").First(n => n.HasClass("item-title")).GetAttributes() + .First(a => a.Name == "href").Value); + } + + HashSet ret = new(); + foreach (string url in urls) + { + DownloadClient.RequestResult requestResult = + downloadClient.MakeRequest(url, (byte)1); + if (requestResult.statusCode != HttpStatusCode.OK) + return Array.Empty(); + + ret.Add(ParseSinglePublicationFromHtml(requestResult.result, url.Split('/')[^1])); + } + + return ret.ToArray(); + } + + private Publication ParseSinglePublicationFromHtml(Stream html, string publicationId) + { + StreamReader reader = new (html); + string htmlString = reader.ReadToEnd(); + HtmlDocument document = new (); + document.LoadHtml(htmlString); + string status = ""; + Dictionary altTitles = new(); + Dictionary? links = null; + HashSet tags = new(); + string? author = null, originalLanguage = null; + int? year = null; + + HtmlNode infoNode = document.DocumentNode.Descendants("div").First(d => d.HasClass("story-info-right")); + + string sortName = infoNode.Descendants("h1").First().InnerText; + + HtmlNode infoTable = infoNode.Descendants().First(d => d.Name == "table"); + + foreach (HtmlNode row in infoTable.Descendants("tr")) + { + string key = row.SelectNodes("td").First().InnerText.ToLower(); + string value = row.SelectNodes("td").Last().InnerText; + string keySanitized = string.Concat(Regex.Matches(key, "[a-z]")); + + switch (keySanitized) + { + case "alternative": + altTitles.Add("",value); + break; + case "authors": + author = value; + break; + case "status": + status = value; + break; + case "genres": + string[] genres = value.Split(" - "); + tags = genres.ToHashSet(); + break; + default: break; + } + } + + string posterUrl = document.DocumentNode.Descendants("span").First(s => s.HasClass("info-image")).Descendants("img").First() + .GetAttributes().First(a => a.Name == "src").Value; + + string coverFileNameInCache = SaveCoverImageToCache(posterUrl, 1); + + string description = document.DocumentNode.Descendants("div").First(d => d.HasClass("panel-story-info-description")) + .InnerText; + + + return new Publication(sortName, author, description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links, + year, originalLanguage, status, publicationId); + } + + public override Chapter[] GetChapters(Publication publication, string language = "") + { + string requestUrl = $"https://manganato.com/{publication.publicationId}"; + DownloadClient.RequestResult requestResult = + downloadClient.MakeRequest(requestUrl, (byte)1); + if (requestResult.statusCode != HttpStatusCode.OK) + return Array.Empty(); + + return ParseChaptersFromHtml(requestResult.result); + } + + private Chapter[] ParseChaptersFromHtml(Stream html) + { + StreamReader reader = new (html); + string htmlString = reader.ReadToEnd(); + HtmlDocument document = new (); + document.LoadHtml(htmlString); + List ret = new(); + + HtmlNode chapterList = document.DocumentNode.Descendants("ul").First(l => l.HasClass("row-content-chapter")); + + foreach (HtmlNode chapterInfo in chapterList.Descendants("li")) + { + string fullString = chapterInfo.Descendants("a").First(d => d.HasClass("chapter-name")).InnerText; + + string? volumeNumber = fullString.Contains("Vol.") ? fullString.Replace("Vol.", "").Split(' ')[0] : null; + string? chapterNumber = fullString.Split(':')[0].Split(' ')[^1]; + string chapterName = string.Concat(fullString.Split(':')[1..]); + string url = chapterInfo.Descendants("a").First(d => d.HasClass("chapter-name")) + .GetAttributeValue("href", ""); + ret.Add(new Chapter(chapterName, volumeNumber, chapterNumber, url)); + } + + return ret.ToArray(); + } + + public override void DownloadChapter(Publication publication, Chapter chapter) + { + string requestUrl = chapter.url; + DownloadClient.RequestResult requestResult = + downloadClient.MakeRequest(requestUrl, (byte)1); + if (requestResult.statusCode != HttpStatusCode.OK) + return; + + string[] imageUrls = ParseImageUrlsFromHtml(requestResult.result); + + string comicInfoPath = Path.GetTempFileName(); + File.WriteAllText(comicInfoPath, GetComicInfoXmlString(publication, chapter, logger)); + + DownloadChapterImages(imageUrls, GetArchiveFilePath(publication, chapter), (byte)1, comicInfoPath, "https://chapmanganato.com/"); + } + + private string[] ParseImageUrlsFromHtml(Stream html) + { + StreamReader reader = new (html); + string htmlString = reader.ReadToEnd(); + HtmlDocument document = new (); + document.LoadHtml(htmlString); + List ret = new(); + + HtmlNode imageContainer = + document.DocumentNode.Descendants("div").First(i => i.HasClass("container-chapter-reader")); + foreach(HtmlNode imageNode in imageContainer.Descendants("img")) + ret.Add(imageNode.GetAttributeValue("src", "")); + + return ret.ToArray(); + } +} \ No newline at end of file diff --git a/Tranga/TaskManager.cs b/Tranga/TaskManager.cs index e7cfaf2..705062e 100644 --- a/Tranga/TaskManager.cs +++ b/Tranga/TaskManager.cs @@ -39,7 +39,11 @@ public class TaskManager this.settings = new TrangaSettings(downloadFolderPath, workingDirectory, newKomga); ExportDataAndSettings(); - this._connectors = new Connector[]{ new MangaDex(downloadFolderPath, imageCachePath, logger) }; + this._connectors = new Connector[] + { + new MangaDex(downloadFolderPath, imageCachePath, logger), + new Manganato(downloadFolderPath, imageCachePath, logger) + }; foreach(Connector cConnector in this._connectors) _taskQueue.Add(cConnector, new List()); @@ -59,7 +63,11 @@ public class TaskManager public TaskManager(TrangaSettings settings, Logger? logger = null) { this.logger = logger; - this._connectors = new Connector[]{ new MangaDex(settings.downloadLocation, settings.coverImageCache, logger) }; + this._connectors = new Connector[] + { + new MangaDex(settings.downloadLocation, settings.coverImageCache, logger), + new Manganato(settings.downloadLocation, settings.coverImageCache, logger) + }; foreach(Connector cConnector in this._connectors) _taskQueue.Add(cConnector, new List()); _allTasks = new HashSet(); diff --git a/Tranga/Tranga.csproj b/Tranga/Tranga.csproj index 6ddc3bb..d735d48 100644 --- a/Tranga/Tranga.csproj +++ b/Tranga/Tranga.csproj @@ -7,6 +7,7 @@ +