From 3b6417eff2eb359129a6472bca2f54da4704d43b Mon Sep 17 00:00:00 2001 From: Glax Date: Wed, 31 Jul 2024 17:48:05 +0200 Subject: [PATCH 01/22] Fix #214 HTML encoded Characters --- Tranga/Manga.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Tranga/Manga.cs b/Tranga/Manga.cs index 171daad..eb9e5d8 100644 --- a/Tranga/Manga.cs +++ b/Tranga/Manga.cs @@ -1,6 +1,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; +using System.Web; using Newtonsoft.Json; using static System.IO.UnixFileMode; @@ -51,11 +52,11 @@ public struct Manga [JsonConstructor] public Manga(string sortName, List authors, string? description, Dictionary altTitles, string[] tags, string? coverUrl, string? coverFileNameInCache, Dictionary? links, int? year, string? originalLanguage, string publicationId, ReleaseStatusByte releaseStatus, string? websiteUrl = null, string? folderName = null, float? ignoreChaptersBelow = 0) { - this.sortName = sortName; - this.authors = authors; - this.description = description; - this.altTitles = altTitles; - this.tags = tags; + this.sortName = HttpUtility.HtmlDecode(sortName); + this.authors = authors.Select(HttpUtility.HtmlDecode).ToList()!; + this.description = HttpUtility.HtmlDecode(description); + this.altTitles = altTitles.ToDictionary(a => HttpUtility.HtmlDecode(a.Key), a => HttpUtility.HtmlDecode(a.Value)); + this.tags = tags.Select(HttpUtility.HtmlDecode).ToArray()!; this.coverFileNameInCache = coverFileNameInCache; this.coverUrl = coverUrl; this.links = links ?? new Dictionary(); From 926c0d5833ef0aa500a72154d05744ae3f552fb8 Mon Sep 17 00:00:00 2001 From: Glax Date: Wed, 31 Jul 2024 19:24:59 +0200 Subject: [PATCH 02/22] fix #214 foldernames --- Tranga/Manga.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tranga/Manga.cs b/Tranga/Manga.cs index eb9e5d8..22fc99a 100644 --- a/Tranga/Manga.cs +++ b/Tranga/Manga.cs @@ -63,7 +63,7 @@ public struct Manga this.year = year; this.originalLanguage = originalLanguage; this.publicationId = publicationId; - this.folderName = folderName ?? string.Concat(LegalCharacters.Matches(sortName)); + this.folderName = folderName ?? string.Concat(LegalCharacters.Matches(HttpUtility.HtmlDecode(sortName))); while (this.folderName.EndsWith('.')) this.folderName = this.folderName.Substring(0, this.folderName.Length - 1); string onlyLowerLetters = string.Concat(this.sortName.ToLower().Where(Char.IsLetter)); From d00881e611cf5f239d5f592fde2d581507436830 Mon Sep 17 00:00:00 2001 From: Glax Date: Thu, 8 Aug 2024 18:58:40 +0200 Subject: [PATCH 03/22] Add Connector ManhuaPlus https://github.com/C9Glax/tranga/issues/213 --- .../MangaConnectorJsonConverter.cs | 2 + Tranga/MangaConnectors/ManhuaPlus.cs | 184 ++++++++++++++++++ Tranga/Tranga.cs | 3 +- 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 Tranga/MangaConnectors/ManhuaPlus.cs diff --git a/Tranga/MangaConnectors/MangaConnectorJsonConverter.cs b/Tranga/MangaConnectors/MangaConnectorJsonConverter.cs index bde2818..0e3dd78 100644 --- a/Tranga/MangaConnectors/MangaConnectorJsonConverter.cs +++ b/Tranga/MangaConnectors/MangaConnectorJsonConverter.cs @@ -38,6 +38,8 @@ public class MangaConnectorJsonConverter : JsonConverter return this._connectors.First(c => c is Bato); case "Manga4Life": return this._connectors.First(c => c is MangaLife); + case "ManhuaPlus": + return this._connectors.First(c => c is ManhuaPlus); } throw new Exception(); diff --git a/Tranga/MangaConnectors/ManhuaPlus.cs b/Tranga/MangaConnectors/ManhuaPlus.cs new file mode 100644 index 0000000..3fe392c --- /dev/null +++ b/Tranga/MangaConnectors/ManhuaPlus.cs @@ -0,0 +1,184 @@ +using System.Net; +using System.Text.RegularExpressions; +using HtmlAgilityPack; +using Tranga.Jobs; + +namespace Tranga.MangaConnectors; + +public class ManhuaPlus : MangaConnector +{ + public ManhuaPlus(GlobalBase clone) : base(clone, "ManhuaPlus") + { + this.downloadClient = new ChromiumDownloadClient(clone); + } + + public override Manga[] GetManga(string publicationTitle = "") + { + Log($"Searching Publications. Term=\"{publicationTitle}\""); + string sanitizedTitle = string.Join(' ', Regex.Matches(publicationTitle, "[A-z]*").Where(str => str.Length > 0)).ToLower(); + string requestUrl = $"https://manhuaplus.org/search?keyword={sanitizedTitle}"; + RequestResult requestResult = + downloadClient.MakeRequest(requestUrl, RequestType.Default); + if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) + return Array.Empty(); + + if (requestResult.htmlDocument is null) + return Array.Empty(); + Manga[] publications = ParsePublicationsFromHtml(requestResult.htmlDocument); + Log($"Retrieved {publications.Length} publications. Term=\"{publicationTitle}\""); + return publications; + } + + private Manga[] ParsePublicationsFromHtml(HtmlDocument document) + { + if (document.DocumentNode.SelectSingleNode("//h1/../..").ChildNodes//I already want to not. + .Any(node => node.InnerText.Contains("No manga found"))) + return Array.Empty(); + + List urls = document.DocumentNode + .SelectNodes("//h1/../..//a[contains(@href, 'https://manhuaplus.org/manga/') and contains(concat(' ',normalize-space(@class),' '),' clamp ') and not(contains(@href, '/chapter'))]") + .Select(mangaNode => mangaNode.GetAttributeValue("href", "")).ToList(); + logger?.WriteLine($"Got {urls.Count} urls."); + + HashSet ret = new(); + foreach (string url in urls) + { + Manga? manga = GetMangaFromUrl(url); + if (manga is not null) + ret.Add((Manga)manga); + } + + return ret.ToArray(); + } + + public override Manga? GetMangaFromId(string publicationId) + { + return GetMangaFromUrl($"https://manhuaplus.org/manga/{publicationId}"); + } + + public override Manga? GetMangaFromUrl(string url) + { + Regex publicationIdRex = new(@"https:\/\/manhuaplus.org\/manga\/(.*)(\/.*)*"); + string publicationId = publicationIdRex.Match(url).Groups[1].Value; + + RequestResult requestResult = this.downloadClient.MakeRequest(url, RequestType.MangaInfo); + if((int)requestResult.statusCode < 300 && (int)requestResult.statusCode >= 200 && requestResult.htmlDocument is not null && requestResult.redirectedToUrl != "https://manhuaplus.org/home") //When manga doesnt exists it redirects to home + return ParseSinglePublicationFromHtml(requestResult.htmlDocument, publicationId, url); + return null; + } + + private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl) + { + string originalLanguage = "", status = ""; + Dictionary altTitles = new(), links = new(); + HashSet tags = new(); + Manga.ReleaseStatusByte releaseStatus = Manga.ReleaseStatusByte.Unreleased; + + HtmlNode posterNode = document.DocumentNode.SelectSingleNode("/html/body/main/div/div/div[2]/div[1]/figure/a/img");//BRUH + Regex posterRex = new(@".*(\/uploads/covers/[a-zA-Z0-9\-\._\~\!\$\&\'\(\)\*\+\,\;\=\:\@]+).*"); + string posterUrl = $"https://manhuaplus.org/{posterRex.Match(posterNode.GetAttributeValue("src", "")).Groups[1].Value}"; + string coverFileNameInCache = SaveCoverImageToCache(posterUrl, publicationId, RequestType.MangaCover); + + HtmlNode titleNode = document.DocumentNode.SelectSingleNode("//h1"); + string sortName = titleNode.InnerText.Replace("\n", ""); + + HtmlNode[] authorsNodes = document.DocumentNode + .SelectNodes("//a[contains(@href, 'https://manhuaplus.org/authors/')]") + .ToArray(); + List authors = new(); + foreach (HtmlNode authorNode in authorsNodes) + authors.Add(authorNode.InnerText); + + HtmlNode[] genreNodes = document.DocumentNode + .SelectNodes("//a[contains(@href, 'https://manhuaplus.org/genres/')]").ToArray(); + foreach (HtmlNode genreNode in genreNodes) + tags.Add(genreNode.InnerText.Replace("\n", "")); + + string yearNodeStr = document.DocumentNode + .SelectSingleNode("//aside//i[contains(concat(' ',normalize-space(@class),' '),' fa-clock ')]/../span").InnerText.Replace("\n", ""); + int year = int.Parse(yearNodeStr.Split(' ')[0].Split('/')[^1]); + + status = document.DocumentNode.SelectSingleNode("//aside//i[contains(concat(' ',normalize-space(@class),' '),' fa-rss ')]/../span").InnerText.Replace("\n", ""); + switch (status.ToLower()) + { + case "cancelled": releaseStatus = Manga.ReleaseStatusByte.Cancelled; break; + case "hiatus": releaseStatus = Manga.ReleaseStatusByte.OnHiatus; break; + case "discontinued": releaseStatus = Manga.ReleaseStatusByte.Cancelled; break; + case "complete": releaseStatus = Manga.ReleaseStatusByte.Completed; break; + case "ongoing": releaseStatus = Manga.ReleaseStatusByte.Continuing; break; + } + + HtmlNode descriptionNode = document.DocumentNode + .SelectSingleNode("//div[@id='syn-target']"); + string description = descriptionNode.InnerText; + + Manga manga = new(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, + coverFileNameInCache, links, + year, originalLanguage, publicationId, releaseStatus, websiteUrl: websiteUrl); + AddMangaToCache(manga); + return manga; + } + + public override Chapter[] GetChapters(Manga manga, string language="en") + { + Log($"Getting chapters {manga}"); + RequestResult result = downloadClient.MakeRequest($"https://manhuaplus.org/manga/{manga.publicationId}", RequestType.Default); + if ((int)result.statusCode < 200 || (int)result.statusCode >= 300 || result.htmlDocument is null) + { + return Array.Empty(); + } + + HtmlNodeCollection chapterNodes = result.htmlDocument.DocumentNode.SelectNodes("//li[contains(concat(' ',normalize-space(@class),' '),' chapter ')]//a"); + string[] urls = chapterNodes.Select(node => node.GetAttributeValue("href", "")).ToArray(); + Regex urlRex = new (@".*\/chapter-([0-9\-]+).*"); + + List chapters = new(); + foreach (string url in urls) + { + Match rexMatch = urlRex.Match(url); + + string volumeNumber = "1"; + string chapterNumber = rexMatch.Groups[1].Value; + string fullUrl = url; + chapters.Add(new Chapter(manga, "", volumeNumber, chapterNumber, fullUrl)); + } + //Return Chapters ordered by Chapter-Number + Log($"Got {chapters.Count} chapters. {manga}"); + return chapters.Order().ToArray(); + } + + public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null) + { + if (progressToken?.cancellationRequested ?? false) + { + progressToken.Cancel(); + return HttpStatusCode.RequestTimeout; + } + + Manga chapterParentManga = chapter.parentManga; + if (progressToken?.cancellationRequested ?? false) + { + progressToken.Cancel(); + return HttpStatusCode.RequestTimeout; + } + + Log($"Retrieving chapter-info {chapter} {chapterParentManga}"); + + RequestResult requestResult = this.downloadClient.MakeRequest(chapter.url, RequestType.Default); + if (requestResult.htmlDocument is null) + { + progressToken?.Cancel(); + return HttpStatusCode.RequestTimeout; + } + + HtmlDocument document = requestResult.htmlDocument; + + HtmlNode[] images = document.DocumentNode.SelectNodes("//a[contains(concat(' ',normalize-space(@class),' '),' readImg ')]/img").ToArray(); + List urls = images.Select(node => node.GetAttributeValue("src", "")).ToList(); + + string comicInfoPath = Path.GetTempFileName(); + File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); + + return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), RequestType.MangaImage, comicInfoPath, progressToken:progressToken); + } +} \ No newline at end of file diff --git a/Tranga/Tranga.cs b/Tranga/Tranga.cs index 5413fa5..b19720e 100644 --- a/Tranga/Tranga.cs +++ b/Tranga/Tranga.cs @@ -24,7 +24,8 @@ public partial class Tranga : GlobalBase new MangaKatana(this), new Mangaworld(this), new Bato(this), - new MangaLife(this) + new MangaLife(this), + new ManhuaPlus(this) }; foreach(DirectoryInfo dir in new DirectoryInfo(Path.GetTempPath()).GetDirectories("trangatemp"))//Cleanup old temp folders dir.Delete(); From bc101363319836aa9b3564c6b78c070b20f81b45 Mon Sep 17 00:00:00 2001 From: Glax Date: Thu, 8 Aug 2024 21:00:37 +0200 Subject: [PATCH 04/22] MangaHere image download sucks, you have to iterate all over all images one by one. Have some extra traffic then, idc. https://github.com/C9Glax/tranga/issues/69 --- Tranga/MangaConnectors/MangaConnector.cs | 4 +- .../MangaConnectorJsonConverter.cs | 2 + Tranga/MangaConnectors/MangaHere.cs | 202 ++++++++++++++++++ Tranga/Tranga.cs | 3 +- 4 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 Tranga/MangaConnectors/MangaHere.cs diff --git a/Tranga/MangaConnectors/MangaConnector.cs b/Tranga/MangaConnectors/MangaConnector.cs index 6dbcb45..5c71de8 100644 --- a/Tranga/MangaConnectors/MangaConnector.cs +++ b/Tranga/MangaConnectors/MangaConnector.cs @@ -222,8 +222,8 @@ public abstract class MangaConnector : GlobalBase if (progressToken?.cancellationRequested ?? false) return HttpStatusCode.RequestTimeout; Log($"Downloading Images for {saveArchiveFilePath}"); - if(progressToken is not null) - progressToken.increments = imageUrls.Length; + if (progressToken is not null) + progressToken.increments += imageUrls.Length; //Check if Publication Directory already exists string directoryPath = Path.GetDirectoryName(saveArchiveFilePath)!; if (!Directory.Exists(directoryPath)) diff --git a/Tranga/MangaConnectors/MangaConnectorJsonConverter.cs b/Tranga/MangaConnectors/MangaConnectorJsonConverter.cs index 0e3dd78..2213a48 100644 --- a/Tranga/MangaConnectors/MangaConnectorJsonConverter.cs +++ b/Tranga/MangaConnectors/MangaConnectorJsonConverter.cs @@ -40,6 +40,8 @@ public class MangaConnectorJsonConverter : JsonConverter return this._connectors.First(c => c is MangaLife); case "ManhuaPlus": return this._connectors.First(c => c is ManhuaPlus); + case "MangaHere": + return this._connectors.First(c => c is MangaHere); } throw new Exception(); diff --git a/Tranga/MangaConnectors/MangaHere.cs b/Tranga/MangaConnectors/MangaHere.cs new file mode 100644 index 0000000..7b13d4d --- /dev/null +++ b/Tranga/MangaConnectors/MangaHere.cs @@ -0,0 +1,202 @@ +using System.Net; +using System.Text.RegularExpressions; +using HtmlAgilityPack; +using Tranga.Jobs; + +namespace Tranga.MangaConnectors; + +public class MangaHere : MangaConnector +{ + public MangaHere(GlobalBase clone) : base(clone, "MangaHere") + { + this.downloadClient = new ChromiumDownloadClient(clone); + } + + public override Manga[] GetManga(string publicationTitle = "") + { + Log($"Searching Publications. Term=\"{publicationTitle}\""); + string sanitizedTitle = string.Join('+', Regex.Matches(publicationTitle, "[A-z]*").Where(str => str.Length > 0)).ToLower(); + string requestUrl = $"https://www.mangahere.cc/search?title={sanitizedTitle}"; + RequestResult requestResult = + downloadClient.MakeRequest(requestUrl, RequestType.Default); + if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null) + return Array.Empty(); + + Manga[] publications = ParsePublicationsFromHtml(requestResult.htmlDocument); + Log($"Retrieved {publications.Length} publications. Term=\"{publicationTitle}\""); + return publications; + } + + private Manga[] ParsePublicationsFromHtml(HtmlDocument document) + { + if (document.DocumentNode.SelectNodes("//div[contains(concat(' ',normalize-space(@class),' '),' container ')]").Any(node => node.ChildNodes.Any(cNode => cNode.HasClass("search-keywords")))) + return Array.Empty(); + + List urls = document.DocumentNode + .SelectNodes("//a[contains(@href, '/manga/') and not(contains(@href, '.html'))]") + .Select(thumb => $"https://www.mangahere.cc{thumb.GetAttributeValue("href", "")}").Distinct().ToList(); + + HashSet ret = new(); + foreach (string url in urls) + { + Manga? manga = GetMangaFromUrl(url); + if (manga is not null) + ret.Add((Manga)manga); + } + + return ret.ToArray(); + } + + public override Manga? GetMangaFromId(string publicationId) + { + return GetMangaFromUrl($"https://www.mangahere.cc/manga/{publicationId}"); + } + + public override Manga? GetMangaFromUrl(string url) + { + RequestResult requestResult = + downloadClient.MakeRequest(url, RequestType.MangaInfo); + if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null) + return null; + + Regex idRex = new (@"https:\/\/www\.mangahere\.[a-z]{0,63}\/manga\/([0-9A-z\-]+).*"); + string id = idRex.Match(url).Groups[1].Value; + return ParseSinglePublicationFromHtml(requestResult.htmlDocument, id, url); + } + + private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl) + { + string originalLanguage = "", status = ""; + Dictionary altTitles = new(), links = new(); + Manga.ReleaseStatusByte releaseStatus = Manga.ReleaseStatusByte.Unreleased; + + //We dont get posters, because same origin bs HtmlNode posterNode = document.DocumentNode.SelectSingleNode("//img[contains(concat(' ',normalize-space(@class),' '),' detail-info-cover-img ')]"); + string posterUrl = "http://static.mangahere.cc/v20230914/mangahere/images/nopicture.jpg"; + string coverFileNameInCache = SaveCoverImageToCache(posterUrl, publicationId, RequestType.MangaCover); + + HtmlNode titleNode = document.DocumentNode.SelectSingleNode("//span[contains(concat(' ',normalize-space(@class),' '),' detail-info-right-title-font ')]"); + string sortName = titleNode.InnerText; + + List authors = document.DocumentNode + .SelectNodes("//p[contains(concat(' ',normalize-space(@class),' '),' detail-info-right-say ')]/a") + .Select(node => node.InnerText) + .ToList(); + + HashSet tags = document.DocumentNode + .SelectNodes("//p[contains(concat(' ',normalize-space(@class),' '),' detail-info-right-tag-list ')]/a") + .Select(node => node.InnerText) + .ToHashSet(); + + status = document.DocumentNode.SelectSingleNode("//span[contains(concat(' ',normalize-space(@class),' '),' detail-info-right-title-tip ')]").InnerText; + switch (status.ToLower()) + { + case "cancelled": releaseStatus = Manga.ReleaseStatusByte.Cancelled; break; + case "hiatus": releaseStatus = Manga.ReleaseStatusByte.OnHiatus; break; + case "discontinued": releaseStatus = Manga.ReleaseStatusByte.Cancelled; break; + case "complete": releaseStatus = Manga.ReleaseStatusByte.Completed; break; + case "ongoing": releaseStatus = Manga.ReleaseStatusByte.Continuing; break; + } + + HtmlNode descriptionNode = document.DocumentNode + .SelectSingleNode("//p[contains(concat(' ',normalize-space(@class),' '),' fullcontent ')]"); + string description = descriptionNode.InnerText; + + Manga manga = new(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, + coverFileNameInCache, links, + null, originalLanguage, publicationId, releaseStatus, websiteUrl: websiteUrl); + AddMangaToCache(manga); + return manga; + } + + public override Chapter[] GetChapters(Manga manga, string language="en") + { + Log($"Getting chapters {manga}"); + string requestUrl = $"https://www.mangahere.cc/manga/{manga.publicationId}"; + RequestResult requestResult = + downloadClient.MakeRequest(requestUrl, RequestType.Default); + if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null) + return Array.Empty(); + + List urls = requestResult.htmlDocument.DocumentNode.SelectNodes("//div[@id='list-2']/ul//li//a[contains(@href, '/manga/')]") + .Select(node => node.GetAttributeValue("href", "")).ToList(); + Regex chapterRex = new(@".*\/manga\/[a-zA-Z0-9\-\._\~\!\$\&\'\(\)\*\+\,\;\=\:\@]+\/v([0-9(TBD)]+)\/c([0-9\.]+)\/.*"); + + List chapters = new(); + foreach (string url in urls) + { + Match rexMatch = chapterRex.Match(url); + + string volumeNumber = rexMatch.Groups[1].Value == "TBD" ? "0" : rexMatch.Groups[1].Value; + string chapterNumber = rexMatch.Groups[2].Value; + string fullUrl = $"https://www.mangahere.cc{url}"; + chapters.Add(new Chapter(manga, "", volumeNumber, chapterNumber, fullUrl)); + } + //Return Chapters ordered by Chapter-Number + Log($"Got {chapters.Count} chapters. {manga}"); + return chapters.Order().ToArray(); + } + + public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null) + { + if (progressToken?.cancellationRequested ?? false) + { + progressToken.Cancel(); + return HttpStatusCode.RequestTimeout; + } + + Manga chapterParentManga = chapter.parentManga; + Log($"Retrieving chapter-info {chapter} {chapterParentManga}"); + + List imageUrls = new(); + + int downloaded = 1; + int images = 1; + string url = string.Join('/', chapter.url.Split('/')[..^1]); + do + { + RequestResult requestResult = + downloadClient.MakeRequest($"{url}/{downloaded}.html", RequestType.Default); + if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) + { + progressToken?.Cancel(); + return requestResult.statusCode; + } + + if (requestResult.htmlDocument is null) + { + progressToken?.Cancel(); + return HttpStatusCode.InternalServerError; + } + + imageUrls.AddRange(ParseImageUrlsFromHtml(requestResult.htmlDocument)); + + images = requestResult.htmlDocument.DocumentNode + .SelectNodes("//a[contains(@href, '/manga/')]") + .MaxBy(node => node.GetAttributeValue("data-page", 0))!.GetAttributeValue("data-page", 0); + if (progressToken is not null) + { + progressToken.increments = images * 2;//we also have to download the images later + progressToken.Increment(); + } + } while (downloaded++ <= images); + + string comicInfoPath = Path.GetTempFileName(); + File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); + + if (progressToken is not null) + progressToken.increments = images;//we blip to normal length, in downloadchapterimages it is increasaed by the amount of urls again + return DownloadChapterImages(imageUrls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), RequestType.MangaImage, comicInfoPath, progressToken:progressToken); + } + + private string[] ParseImageUrlsFromHtml(HtmlDocument document) + { + return document.DocumentNode + .SelectNodes("//img[contains(concat(' ',normalize-space(@class),' '),' reader-main-img ')]") + .Select(node => + { + string url = node.GetAttributeValue("src", ""); + return url.StartsWith("//") ? $"https:{url}" : url; + }) + .ToArray(); + } +} \ No newline at end of file diff --git a/Tranga/Tranga.cs b/Tranga/Tranga.cs index b19720e..1471365 100644 --- a/Tranga/Tranga.cs +++ b/Tranga/Tranga.cs @@ -25,7 +25,8 @@ public partial class Tranga : GlobalBase new Mangaworld(this), new Bato(this), new MangaLife(this), - new ManhuaPlus(this) + new ManhuaPlus(this), + new MangaHere(this), }; foreach(DirectoryInfo dir in new DirectoryInfo(Path.GetTempPath()).GetDirectories("trangatemp"))//Cleanup old temp folders dir.Delete(); From 200a22228f3dcedfc21d22eb14e7cbf2079aa230 Mon Sep 17 00:00:00 2001 From: Glax Date: Thu, 8 Aug 2024 21:02:13 +0200 Subject: [PATCH 05/22] add log output for Mangahere https://github.com/C9Glax/tranga/issues/69 --- Tranga/MangaConnectors/MangaHere.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Tranga/MangaConnectors/MangaHere.cs b/Tranga/MangaConnectors/MangaHere.cs index 7b13d4d..fc9406e 100644 --- a/Tranga/MangaConnectors/MangaHere.cs +++ b/Tranga/MangaConnectors/MangaHere.cs @@ -173,6 +173,7 @@ public class MangaHere : MangaConnector images = requestResult.htmlDocument.DocumentNode .SelectNodes("//a[contains(@href, '/manga/')]") .MaxBy(node => node.GetAttributeValue("data-page", 0))!.GetAttributeValue("data-page", 0); + logger?.WriteLine($"MangaHere speciality: Get Image-url {downloaded}/{images}"); if (progressToken is not null) { progressToken.increments = images * 2;//we also have to download the images later From 34dd78810dfd6c5af58cf9ce5ca9a4bb84c08fa2 Mon Sep 17 00:00:00 2001 From: Glax Date: Thu, 8 Aug 2024 21:09:08 +0200 Subject: [PATCH 06/22] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6137396..05a0d79 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ Tranga can download Chapters and Metadata from "Scanlation" sites such as - [Mangaworld.bz](https://www.mangaworld.bz/) (it) - [Bato.to](https://bato.to/v3x) (en) - [Manga4Life](https://manga4life.com) (en) +- [ManhuaPlus](https://manhuaplus.org/) (en) +- [MangaHere](https://www.mangahere.cc/) (en) (Their covers suck) - ❓ Open an [issue](https://github.com/C9Glax/tranga/issues) and trigger a library-scan with [Komga](https://komga.org/) and [Kavita](https://www.kavitareader.com/). From 21af75f4106c718e9ae38ae0926beb176a15205e Mon Sep 17 00:00:00 2001 From: Glax Date: Tue, 20 Aug 2024 20:47:13 +0200 Subject: [PATCH 07/22] Faster download for images-urls. #224 --- Tranga/MangaConnectors/ChromiumDownloadClient.cs | 15 +++++++++++++-- Tranga/MangaConnectors/DownloadClient.cs | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Tranga/MangaConnectors/ChromiumDownloadClient.cs b/Tranga/MangaConnectors/ChromiumDownloadClient.cs index 8ddf850..c63e85a 100644 --- a/Tranga/MangaConnectors/ChromiumDownloadClient.cs +++ b/Tranga/MangaConnectors/ChromiumDownloadClient.cs @@ -1,5 +1,6 @@ using System.Net; using System.Text; +using System.Text.RegularExpressions; using HtmlAgilityPack; using PuppeteerSharp; using PuppeteerSharp.Input; @@ -11,10 +12,11 @@ internal class ChromiumDownloadClient : DownloadClient private IBrowser browser { get; set; } private const string ChromiumVersion = "1154303"; private const int StartTimeoutMs = 30000; + private HttpDownloadClient _httpDownloadClient; private async Task DownloadBrowser() { - BrowserFetcher browserFetcher = new BrowserFetcher(); + BrowserFetcher browserFetcher = new (); foreach(string rev in browserFetcher.LocalRevisions().Where(rev => rev != ChromiumVersion)) browserFetcher.Remove(rev); if (!browserFetcher.LocalRevisions().Contains(ChromiumVersion)) @@ -58,9 +60,18 @@ internal class ChromiumDownloadClient : DownloadClient public ChromiumDownloadClient(GlobalBase clone) : base(clone) { this.browser = DownloadBrowser().Result; + _httpDownloadClient = new(this); } - protected override RequestResult MakeRequestInternal(string url, string? referrer = null, string? clickButton = null) + private readonly Regex _imageUrlRex = new(@"https?:\/\/.*\.(?:p?jpe?g|gif|a?png|bmp|avif|webp)(\?.*)?"); + internal override RequestResult MakeRequestInternal(string url, string? referrer = null, string? clickButton = null) + { + return _imageUrlRex.IsMatch(url) + ? _httpDownloadClient.MakeRequestInternal(url, referrer) + : MakeRequestBrowser(url, referrer, clickButton); + } + + private RequestResult MakeRequestBrowser(string url, string? referrer = null, string? clickButton = null) { IPage page = this.browser.NewPageAsync().Result; page.DefaultTimeout = 10000; diff --git a/Tranga/MangaConnectors/DownloadClient.cs b/Tranga/MangaConnectors/DownloadClient.cs index 0e9b343..f6f876d 100644 --- a/Tranga/MangaConnectors/DownloadClient.cs +++ b/Tranga/MangaConnectors/DownloadClient.cs @@ -40,6 +40,6 @@ internal abstract class DownloadClient : GlobalBase return result; } - protected abstract RequestResult MakeRequestInternal(string url, string? referrer = null, string? clickButton = null); + internal abstract RequestResult MakeRequestInternal(string url, string? referrer = null, string? clickButton = null); public abstract void Close(); } \ No newline at end of file From ea37e81ece8c191f7dbf4d556e1bb64f9f69d774 Mon Sep 17 00:00:00 2001 From: Glax Date: Tue, 20 Aug 2024 20:53:03 +0200 Subject: [PATCH 08/22] Fix last commit --- Tranga/MangaConnectors/ChromiumDownloadClient.cs | 3 +-- Tranga/MangaConnectors/HttpDownloadClient.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Tranga/MangaConnectors/ChromiumDownloadClient.cs b/Tranga/MangaConnectors/ChromiumDownloadClient.cs index c63e85a..d7e6523 100644 --- a/Tranga/MangaConnectors/ChromiumDownloadClient.cs +++ b/Tranga/MangaConnectors/ChromiumDownloadClient.cs @@ -3,7 +3,6 @@ using System.Text; using System.Text.RegularExpressions; using HtmlAgilityPack; using PuppeteerSharp; -using PuppeteerSharp.Input; namespace Tranga.MangaConnectors; @@ -12,7 +11,7 @@ internal class ChromiumDownloadClient : DownloadClient private IBrowser browser { get; set; } private const string ChromiumVersion = "1154303"; private const int StartTimeoutMs = 30000; - private HttpDownloadClient _httpDownloadClient; + private readonly HttpDownloadClient _httpDownloadClient; private async Task DownloadBrowser() { diff --git a/Tranga/MangaConnectors/HttpDownloadClient.cs b/Tranga/MangaConnectors/HttpDownloadClient.cs index 46a0008..3b2e5c3 100644 --- a/Tranga/MangaConnectors/HttpDownloadClient.cs +++ b/Tranga/MangaConnectors/HttpDownloadClient.cs @@ -16,7 +16,7 @@ internal class HttpDownloadClient : DownloadClient Client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", settings.userAgent); } - protected override RequestResult MakeRequestInternal(string url, string? referrer = null, string? clickButton = null) + internal override RequestResult MakeRequestInternal(string url, string? referrer = null, string? clickButton = null) { if(clickButton is not null) Log("Can not click button on static site."); From f4336f977749c9c01c37e10f3cad838f0c5272c6 Mon Sep 17 00:00:00 2001 From: Glax Date: Mon, 26 Aug 2024 10:35:16 +0200 Subject: [PATCH 09/22] #227 Mangasee Return results that have similarity over 95% or at least top ten results --- Tranga/MangaConnectors/Mangasee.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Tranga/MangaConnectors/Mangasee.cs b/Tranga/MangaConnectors/Mangasee.cs index 2de2b2f..b10421c 100644 --- a/Tranga/MangaConnectors/Mangasee.cs +++ b/Tranga/MangaConnectors/Mangasee.cs @@ -61,21 +61,28 @@ public class Mangasee : MangaConnector } } + private readonly string[] _filterWords = {"a", "the", "of", "as", "to", "no", "for", "on", "with", "be", "and", "in", "wa", "at", "be", "ni"}; + private string ToFilteredString(string input) => string.Join(' ', input.ToLower().Split(' ').Where(word => _filterWords.Contains(word))); private SearchResult[] FilteredResults(string publicationTitle, SearchResult[] unfilteredSearchResults) { Dictionary similarity = new(); foreach (SearchResult sr in unfilteredSearchResults) { List scores = new(); - foreach (string se in sr.a) - scores.Add(NeedlemanWunschStringUtil.CalculateSimilarity(se.ToLower(), publicationTitle.ToLower())); - scores.Add(NeedlemanWunschStringUtil.CalculateSimilarity(sr.s.ToLower(), publicationTitle.ToLower())); + string filteredPublicationString = ToFilteredString(publicationTitle); + string filteredSString = ToFilteredString(sr.s); + scores.Add(NeedlemanWunschStringUtil.CalculateSimilarity(filteredSString, filteredPublicationString)); + foreach (string srA in sr.a) + { + string filteredAString = ToFilteredString(srA); + scores.Add(NeedlemanWunschStringUtil.CalculateSimilarity(filteredAString, filteredPublicationString)); + } similarity.Add(sr, scores.Sum() / scores.Count); } - SearchResult[] similarity90 = similarity.Where(s => s.Value < 10).Select(s => s.Key).ToArray(); - - return similarity90; + List ret = similarity.OrderBy(s => s.Value).Take(10).Select(s => s.Key).ToList(); + ret.AddRange(similarity.Where(s => s.Value < 5).Select(s => s.Key)); + return ret.ToArray(); } public override Manga? GetMangaFromId(string publicationId) From db73af3bdd802be00b51b9f6c5370aa99af2a847 Mon Sep 17 00:00:00 2001 From: Glax Date: Mon, 26 Aug 2024 10:38:45 +0200 Subject: [PATCH 10/22] Fix crash when outputstream closes before response could be sent. #227 --- Tranga/Server.cs | 60 ++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/Tranga/Server.cs b/Tranga/Server.cs index e2f19f0..73ab7b6 100644 --- a/Tranga/Server.cs +++ b/Tranga/Server.cs @@ -712,6 +712,10 @@ public class Server : GlobalBase private void SendResponse(HttpStatusCode statusCode, HttpListenerResponse response, object? content = null) { + if (response.OutputStream.CanWrite == false) + { + Log($"No response sent to request: Stream closed before response could be sent."); + } //Log($"Response: {statusCode} {content}"); response.StatusCode = (int)statusCode; response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With"); @@ -719,43 +723,43 @@ public class Server : GlobalBase response.AddHeader("Access-Control-Max-Age", "1728000"); response.AppendHeader("Access-Control-Allow-Origin", "*"); - if (content is not Stream) + try { - response.ContentType = "application/json"; - try + if (content is not Stream) { + response.ContentType = "application/json"; response.OutputStream.Write(content is not null ? Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(content)) : Array.Empty()); - response.OutputStream.Close(); } - catch (HttpListenerException e) + else if (content is FileStream stream) { - Log(e.ToString()); + string contentType = stream.Name.Split('.')[^1]; + switch (contentType.ToLower()) + { + case "gif": + response.ContentType = "image/gif"; + break; + case "png": + response.ContentType = "image/png"; + break; + case "jpg": + case "jpeg": + response.ContentType = "image/jpeg"; + break; + case "log": + response.ContentType = "text/plain"; + break; + } + + stream.CopyTo(response.OutputStream); + stream.Close(); } - } - else if(content is FileStream stream) - { - string contentType = stream.Name.Split('.')[^1]; - switch (contentType.ToLower()) - { - case "gif": - response.ContentType = "image/gif"; - break; - case "png": - response.ContentType = "image/png"; - break; - case "jpg": - case "jpeg": - response.ContentType = "image/jpeg"; - break; - case "log": - response.ContentType = "text/plain"; - break; - } - stream.CopyTo(response.OutputStream); response.OutputStream.Close(); - stream.Close(); + } + catch (HttpListenerException e) + { + Log(e.ToString()); } } } \ No newline at end of file From 69d68845171768dcc6371db7173a39596b9ab4d2 Mon Sep 17 00:00:00 2001 From: Glax Date: Mon, 26 Aug 2024 11:17:59 +0200 Subject: [PATCH 11/22] #227 Fix wrong filtering, only return top 10 results --- Tranga/MangaConnectors/Mangasee.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tranga/MangaConnectors/Mangasee.cs b/Tranga/MangaConnectors/Mangasee.cs index b10421c..1bc7445 100644 --- a/Tranga/MangaConnectors/Mangasee.cs +++ b/Tranga/MangaConnectors/Mangasee.cs @@ -62,7 +62,7 @@ public class Mangasee : MangaConnector } private readonly string[] _filterWords = {"a", "the", "of", "as", "to", "no", "for", "on", "with", "be", "and", "in", "wa", "at", "be", "ni"}; - private string ToFilteredString(string input) => string.Join(' ', input.ToLower().Split(' ').Where(word => _filterWords.Contains(word))); + private string ToFilteredString(string input) => string.Join(' ', input.ToLower().Split(' ').Where(word => _filterWords.Contains(word) == false)); private SearchResult[] FilteredResults(string publicationTitle, SearchResult[] unfilteredSearchResults) { Dictionary similarity = new(); @@ -81,7 +81,6 @@ public class Mangasee : MangaConnector } List ret = similarity.OrderBy(s => s.Value).Take(10).Select(s => s.Key).ToList(); - ret.AddRange(similarity.Where(s => s.Value < 5).Select(s => s.Key)); return ret.ToArray(); } From 63b493fa9ce4d858a8577584a3ae8959e124275d Mon Sep 17 00:00:00 2001 From: Glax Date: Mon, 26 Aug 2024 12:36:35 +0200 Subject: [PATCH 12/22] Rework TrangaSettings --- CLI/Program.cs | 33 +--- Tranga/Chapter.cs | 12 +- Tranga/GlobalBase.cs | 25 ++- Tranga/Jobs/DownloadNewChapters.cs | 2 +- Tranga/Jobs/JobBoss.cs | 12 +- Tranga/Jobs/UpdateMetadata.cs | 2 +- Tranga/Manga.cs | 4 +- Tranga/MangaConnectors/Bato.cs | 2 +- Tranga/MangaConnectors/DownloadClient.cs | 6 +- Tranga/MangaConnectors/HttpDownloadClient.cs | 2 +- Tranga/MangaConnectors/MangaConnector.cs | 10 +- Tranga/MangaConnectors/MangaDex.cs | 2 +- Tranga/MangaConnectors/MangaHere.cs | 2 +- Tranga/MangaConnectors/MangaKatana.cs | 2 +- Tranga/MangaConnectors/MangaLife.cs | 2 +- Tranga/MangaConnectors/Manganato.cs | 2 +- Tranga/MangaConnectors/Mangasee.cs | 2 +- Tranga/MangaConnectors/Mangaworld.cs | 2 +- Tranga/MangaConnectors/ManhuaPlus.cs | 2 +- Tranga/Server.cs | 38 ++--- Tranga/Tranga.cs | 5 +- Tranga/TrangaArgs.cs | 32 +--- Tranga/TrangaSettings.cs | 163 ++++++++++--------- 23 files changed, 163 insertions(+), 201 deletions(-) diff --git a/CLI/Program.cs b/CLI/Program.cs index ec21751..f927ea2 100644 --- a/CLI/Program.cs +++ b/CLI/Program.cs @@ -47,37 +47,16 @@ internal sealed class TrangaCli : Command string? logFolderPath = settings.fileLoggerPath ?? ""; Logger logger = new(enabledLoggers.ToArray(), Console.Out, Console.OutputEncoding, logFolderPath); - TrangaSettings? trangaSettings = null; - - if (settings.downloadLocation is not null && settings.workingDirectory is not null) - { - trangaSettings = new TrangaSettings(settings.downloadLocation, settings.workingDirectory); - }else if (settings.downloadLocation is not null) - { - if (trangaSettings is null) - trangaSettings = new TrangaSettings(downloadLocation: settings.downloadLocation); - else - trangaSettings = new TrangaSettings(downloadLocation: settings.downloadLocation, settings.workingDirectory); - }else if (settings.workingDirectory is not null) - { - if (trangaSettings is null) - trangaSettings = new TrangaSettings(downloadLocation: settings.workingDirectory); - else - trangaSettings = new TrangaSettings(settings.downloadLocation, settings.workingDirectory); - } - else - { - trangaSettings = new TrangaSettings(); - } - - Directory.CreateDirectory(trangaSettings.downloadLocation); - Directory.CreateDirectory(trangaSettings.workingDirectory); + if(settings.workingDirectory is not null) + TrangaSettings.LoadFromWorkingDirectory(settings.workingDirectory); + if(settings.downloadLocation is not null) + TrangaSettings.CreateOrUpdate(downloadDirectory: settings.downloadLocation); Tranga.Tranga? api = null; Thread trangaApi = new Thread(() => { - api = new(logger, trangaSettings); + api = new(logger); }); trangaApi.Start(); @@ -120,7 +99,7 @@ internal sealed class TrangaCli : Command parameters.Add(new ValueTuple(name, value)); } - string requestString = $"http://localhost:{trangaSettings.apiPortNumber}/{requestPath}"; + string requestString = $"http://localhost:{TrangaSettings.apiPortNumber}/{requestPath}"; if (parameters.Any()) { requestString += "?"; diff --git a/Tranga/Chapter.cs b/Tranga/Chapter.cs index 4677d7b..369b8fc 100644 --- a/Tranga/Chapter.cs +++ b/Tranga/Chapter.cs @@ -87,15 +87,15 @@ public readonly struct Chapter : IComparable /// Checks if a chapter-archive is already present /// /// true if chapter is present - internal bool CheckChapterIsDownloaded(string downloadLocation) + internal bool CheckChapterIsDownloaded() { - if (!Directory.Exists(Path.Join(downloadLocation, parentManga.folderName))) + if (!Directory.Exists(Path.Join(TrangaSettings.downloadLocation, parentManga.folderName))) return false; - FileInfo[] archives = new DirectoryInfo(Path.Join(downloadLocation, parentManga.folderName)).GetFiles().Where(file => file.Name.Split('.')[^1] == "cbz").ToArray(); + FileInfo[] archives = new DirectoryInfo(Path.Join(TrangaSettings.downloadLocation, parentManga.folderName)).GetFiles().Where(file => file.Name.Split('.')[^1] == "cbz").ToArray(); Regex volChRex = new(@"(?:Vol(?:ume)?\.([0-9]+)\D*)?Ch(?:apter)?\.([0-9]+(?:\.[0-9]+)*)"); Chapter t = this; - string thisPath = GetArchiveFilePath(downloadLocation); + string thisPath = GetArchiveFilePath(); FileInfo? archive = archives.FirstOrDefault(archive => { Match m = volChRex.Match(archive.Name); @@ -112,9 +112,9 @@ public readonly struct Chapter : IComparable /// Creates full file path of chapter-archive /// /// Filepath - internal string GetArchiveFilePath(string downloadLocation) + internal string GetArchiveFilePath() { - return Path.Join(downloadLocation, parentManga.folderName, $"{parentManga.folderName} - {this.fileName}.cbz"); + return Path.Join(TrangaSettings.downloadLocation, parentManga.folderName, $"{parentManga.folderName} - {this.fileName}.cbz"); } /// diff --git a/Tranga/GlobalBase.cs b/Tranga/GlobalBase.cs index f372f2c..49a0d79 100644 --- a/Tranga/GlobalBase.cs +++ b/Tranga/GlobalBase.cs @@ -11,7 +11,6 @@ public abstract class GlobalBase { [JsonIgnore] public Logger? logger { get; init; } - protected TrangaSettings settings { get; init; } protected HashSet notificationConnectors { get; init; } protected HashSet libraryConnectors { get; init; } private Dictionary cachedPublications { get; init; } @@ -21,18 +20,16 @@ public abstract class GlobalBase protected GlobalBase(GlobalBase clone) { this.logger = clone.logger; - this.settings = clone.settings; this.notificationConnectors = clone.notificationConnectors; this.libraryConnectors = clone.libraryConnectors; this.cachedPublications = clone.cachedPublications; } - protected GlobalBase(Logger? logger, TrangaSettings settings) + protected GlobalBase(Logger? logger) { this.logger = logger; - this.settings = settings; - this.notificationConnectors = settings.LoadNotificationConnectors(this); - this.libraryConnectors = settings.LoadLibraryConnectors(this); + this.notificationConnectors = TrangaSettings.LoadNotificationConnectors(this); + this.libraryConnectors = TrangaSettings.LoadLibraryConnectors(this); this.cachedPublications = new(); } @@ -81,20 +78,20 @@ public abstract class GlobalBase notificationConnectors.RemoveWhere(nc => nc.notificationConnectorType == notificationConnector.notificationConnectorType); notificationConnectors.Add(notificationConnector); - while(IsFileInUse(settings.notificationConnectorsFilePath)) + while(IsFileInUse(TrangaSettings.notificationConnectorsFilePath)) Thread.Sleep(100); Log("Exporting notificationConnectors"); - File.WriteAllText(settings.notificationConnectorsFilePath, JsonConvert.SerializeObject(notificationConnectors)); + File.WriteAllText(TrangaSettings.notificationConnectorsFilePath, JsonConvert.SerializeObject(notificationConnectors)); } protected void DeleteNotificationConnector(NotificationConnector.NotificationConnectorType notificationConnectorType) { Log($"Removing {notificationConnectorType}"); notificationConnectors.RemoveWhere(nc => nc.notificationConnectorType == notificationConnectorType); - while(IsFileInUse(settings.notificationConnectorsFilePath)) + while(IsFileInUse(TrangaSettings.notificationConnectorsFilePath)) Thread.Sleep(100); Log("Exporting notificationConnectors"); - File.WriteAllText(settings.notificationConnectorsFilePath, JsonConvert.SerializeObject(notificationConnectors)); + File.WriteAllText(TrangaSettings.notificationConnectorsFilePath, JsonConvert.SerializeObject(notificationConnectors)); } protected void UpdateLibraries() @@ -109,20 +106,20 @@ public abstract class GlobalBase libraryConnectors.RemoveWhere(lc => lc.libraryType == libraryConnector.libraryType); libraryConnectors.Add(libraryConnector); - while(IsFileInUse(settings.libraryConnectorsFilePath)) + while(IsFileInUse(TrangaSettings.libraryConnectorsFilePath)) Thread.Sleep(100); Log("Exporting libraryConnectors"); - File.WriteAllText(settings.libraryConnectorsFilePath, JsonConvert.SerializeObject(libraryConnectors, Formatting.Indented)); + File.WriteAllText(TrangaSettings.libraryConnectorsFilePath, JsonConvert.SerializeObject(libraryConnectors, Formatting.Indented)); } protected void DeleteLibraryConnector(LibraryConnector.LibraryType libraryType) { Log($"Removing {libraryType}"); libraryConnectors.RemoveWhere(lc => lc.libraryType == libraryType); - while(IsFileInUse(settings.libraryConnectorsFilePath)) + while(IsFileInUse(TrangaSettings.libraryConnectorsFilePath)) Thread.Sleep(100); Log("Exporting libraryConnectors"); - File.WriteAllText(settings.libraryConnectorsFilePath, JsonConvert.SerializeObject(libraryConnectors, Formatting.Indented)); + File.WriteAllText(TrangaSettings.libraryConnectorsFilePath, JsonConvert.SerializeObject(libraryConnectors, Formatting.Indented)); } protected bool IsFileInUse(string filePath) => IsFileInUse(filePath, this.logger); diff --git a/Tranga/Jobs/DownloadNewChapters.cs b/Tranga/Jobs/DownloadNewChapters.cs index ebcbde7..29c3e60 100644 --- a/Tranga/Jobs/DownloadNewChapters.cs +++ b/Tranga/Jobs/DownloadNewChapters.cs @@ -33,7 +33,7 @@ public class DownloadNewChapters : Job protected override IEnumerable ExecuteReturnSubTasksInternal(JobBoss jobBoss) { - manga.SaveSeriesInfoJson(settings.downloadLocation); + manga.SaveSeriesInfoJson(); Chapter[] chapters = mangaConnector.GetNewChapters(manga, this.translatedLanguage); this.progressToken.increments = chapters.Length; List jobs = new(); diff --git a/Tranga/Jobs/JobBoss.cs b/Tranga/Jobs/JobBoss.cs index c5324d0..610d70c 100644 --- a/Tranga/Jobs/JobBoss.cs +++ b/Tranga/Jobs/JobBoss.cs @@ -140,15 +140,15 @@ public class JobBoss : GlobalBase private void LoadJobsList(HashSet connectors) { - if (!Directory.Exists(settings.jobsFolderPath)) //No jobs to load + if (!Directory.Exists(TrangaSettings.jobsFolderPath)) //No jobs to load { - Directory.CreateDirectory(settings.jobsFolderPath); + Directory.CreateDirectory(TrangaSettings.jobsFolderPath); return; } Regex idRex = new (@"(.*)\.json"); //Load json-job-files - foreach (FileInfo file in new DirectoryInfo(settings.jobsFolderPath).EnumerateFiles().Where(fileInfo => idRex.IsMatch(fileInfo.Name))) + foreach (FileInfo file in new DirectoryInfo(TrangaSettings.jobsFolderPath).EnumerateFiles().Where(fileInfo => idRex.IsMatch(fileInfo.Name))) { Log($"Adding {file.Name}"); Job? job = JsonConvert.DeserializeObject(File.ReadAllText(file.FullName), @@ -180,14 +180,14 @@ public class JobBoss : GlobalBase AddMangaToCache(dncJob.manga); } - string[] coverFiles = Directory.GetFiles(settings.coverImageCache); + string[] coverFiles = Directory.GetFiles(TrangaSettings.coverImageCache); foreach(string fileName in coverFiles.Where(fileName => !GetAllCachedManga().Any(manga => manga.coverFileNameInCache == fileName))) File.Delete(fileName); } internal void UpdateJobFile(Job job, string? oldFile = null) { - string newJobFilePath = Path.Join(settings.jobsFolderPath, $"{job.id}.json"); + string newJobFilePath = Path.Join(TrangaSettings.jobsFolderPath, $"{job.id}.json"); if (!this.jobs.Any(jjob => jjob.id == job.id)) { @@ -234,7 +234,7 @@ public class JobBoss : GlobalBase //Remove files with jobs not in this.jobs-list Regex idRex = new (@"(.*)\.json"); - foreach (FileInfo file in new DirectoryInfo(settings.jobsFolderPath).EnumerateFiles()) + foreach (FileInfo file in new DirectoryInfo(TrangaSettings.jobsFolderPath).EnumerateFiles()) { if (idRex.IsMatch(file.Name)) { diff --git a/Tranga/Jobs/UpdateMetadata.cs b/Tranga/Jobs/UpdateMetadata.cs index 4dc3ae7..72b5a60 100644 --- a/Tranga/Jobs/UpdateMetadata.cs +++ b/Tranga/Jobs/UpdateMetadata.cs @@ -34,7 +34,7 @@ public class UpdateMetadata : Job } this.manga = manga.WithMetadata(updatedManga); - this.manga.SaveSeriesInfoJson(settings.downloadLocation, true); + this.manga.SaveSeriesInfoJson(true); this.mangaConnector.CopyCoverFromCacheToDownloadLocation(manga); foreach (Job job in jobBoss.GetJobsLike(publication: this.manga)) { diff --git a/Tranga/Manga.cs b/Tranga/Manga.cs index 22fc99a..5ebad19 100644 --- a/Tranga/Manga.cs +++ b/Tranga/Manga.cs @@ -140,9 +140,9 @@ public struct Manga latestChapterDownloaded = latestChapterDownloaded < chapterNumber ? chapterNumber : latestChapterDownloaded; } - public void SaveSeriesInfoJson(string downloadDirectory, bool overwrite = false) + public void SaveSeriesInfoJson(bool overwrite = false) { - string publicationFolder = CreatePublicationFolder(downloadDirectory); + string publicationFolder = CreatePublicationFolder(TrangaSettings.downloadLocation); string seriesInfoPath = Path.Join(publicationFolder, "series.json"); if(overwrite || (!overwrite && !File.Exists(seriesInfoPath))) File.WriteAllText(seriesInfoPath,this.GetSeriesInfoJson()); diff --git a/Tranga/MangaConnectors/Bato.cs b/Tranga/MangaConnectors/Bato.cs index d50081b..5b5f370 100644 --- a/Tranga/MangaConnectors/Bato.cs +++ b/Tranga/MangaConnectors/Bato.cs @@ -193,7 +193,7 @@ public class Bato : MangaConnector string comicInfoPath = Path.GetTempFileName(); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); - return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), RequestType.MangaImage, comicInfoPath, "https://mangakatana.com/", progressToken:progressToken); + return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(), RequestType.MangaImage, comicInfoPath, "https://mangakatana.com/", progressToken:progressToken); } private string[] ParseImageUrlsFromHtml(string mangaUrl) diff --git a/Tranga/MangaConnectors/DownloadClient.cs b/Tranga/MangaConnectors/DownloadClient.cs index f6f876d..298774f 100644 --- a/Tranga/MangaConnectors/DownloadClient.cs +++ b/Tranga/MangaConnectors/DownloadClient.cs @@ -14,15 +14,15 @@ internal abstract class DownloadClient : GlobalBase public RequestResult MakeRequest(string url, RequestType requestType, string? referrer = null, string? clickButton = null) { - if (!settings.requestLimits.ContainsKey(requestType)) + if (!TrangaSettings.requestLimits.ContainsKey(requestType)) { Log("RequestType not configured for rate-limit."); return new RequestResult(HttpStatusCode.NotAcceptable, null, Stream.Null); } - int rateLimit = settings.userAgent == TrangaSettings.DefaultUserAgent + int rateLimit = TrangaSettings.userAgent == TrangaSettings.DefaultUserAgent ? TrangaSettings.DefaultRequestLimits[requestType] - : settings.requestLimits[requestType]; + : TrangaSettings.requestLimits[requestType]; TimeSpan timeBetweenRequests = TimeSpan.FromMinutes(1).Divide(rateLimit); _lastExecutedRateLimit.TryAdd(requestType, DateTime.Now.Subtract(timeBetweenRequests)); diff --git a/Tranga/MangaConnectors/HttpDownloadClient.cs b/Tranga/MangaConnectors/HttpDownloadClient.cs index 3b2e5c3..238306b 100644 --- a/Tranga/MangaConnectors/HttpDownloadClient.cs +++ b/Tranga/MangaConnectors/HttpDownloadClient.cs @@ -13,7 +13,7 @@ internal class HttpDownloadClient : DownloadClient public HttpDownloadClient(GlobalBase clone) : base(clone) { - Client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", settings.userAgent); + Client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", TrangaSettings.userAgent); } internal override RequestResult MakeRequestInternal(string url, string? referrer = null, string? clickButton = null) diff --git a/Tranga/MangaConnectors/MangaConnector.cs b/Tranga/MangaConnectors/MangaConnector.cs index 5c71de8..5af28e5 100644 --- a/Tranga/MangaConnectors/MangaConnector.cs +++ b/Tranga/MangaConnectors/MangaConnector.cs @@ -23,7 +23,7 @@ public abstract class MangaConnector : GlobalBase protected MangaConnector(GlobalBase clone, string name) : base(clone) { this.name = name; - Directory.CreateDirectory(settings.coverImageCache); + Directory.CreateDirectory(TrangaSettings.coverImageCache); } public string name { get; } //Name of the Connector (e.g. Website) @@ -65,7 +65,7 @@ public abstract class MangaConnector : GlobalBase Log($"Checking for duplicates {manga}"); List newChaptersList = allChapters.Where(nChapter => float.TryParse(nChapter.chapterNumber, numberFormatDecimalPoint, out float chapterNumber) && chapterNumber > manga.ignoreChaptersBelow - && !nChapter.CheckChapterIsDownloaded(settings.downloadLocation)).ToList(); + && !nChapter.CheckChapterIsDownloaded()).ToList(); Log($"{newChaptersList.Count} new chapters. {manga}"); try { @@ -167,7 +167,7 @@ public abstract class MangaConnector : GlobalBase { Log($"Copy cover {manga}"); //Check if Publication already has a Folder and cover - string publicationFolder = manga.CreatePublicationFolder(settings.downloadLocation); + string publicationFolder = manga.CreatePublicationFolder(TrangaSettings.downloadLocation); DirectoryInfo dirInfo = new (publicationFolder); if (dirInfo.EnumerateFiles().Any(info => info.Name.Contains("cover", StringComparison.InvariantCultureIgnoreCase))) { @@ -291,7 +291,7 @@ public abstract class MangaConnector : GlobalBase //https?:\/\/[a-zA-Z0-9-]+\.([a-zA-Z0-9-]+\.[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+)) for only second level domains Match match = urlRex.Match(url); string filename = $"{match.Groups[1].Value}-{mangaInternalId}.{match.Groups[3].Value}"; - string saveImagePath = Path.Join(settings.coverImageCache, filename); + string saveImagePath = Path.Join(TrangaSettings.coverImageCache, filename); if (File.Exists(saveImagePath)) return saveImagePath; @@ -299,7 +299,7 @@ public abstract class MangaConnector : GlobalBase RequestResult coverResult = downloadClient.MakeRequest(url, requestType); using MemoryStream ms = new(); coverResult.result.CopyTo(ms); - Directory.CreateDirectory(settings.coverImageCache); + Directory.CreateDirectory(TrangaSettings.coverImageCache); File.WriteAllBytes(saveImagePath, ms.ToArray()); Log($"Saving cover to {saveImagePath}"); return saveImagePath; diff --git a/Tranga/MangaConnectors/MangaDex.cs b/Tranga/MangaConnectors/MangaDex.cs index 60f05fd..005da07 100644 --- a/Tranga/MangaConnectors/MangaDex.cs +++ b/Tranga/MangaConnectors/MangaDex.cs @@ -290,6 +290,6 @@ public class MangaDex : MangaConnector File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); //Download Chapter-Images - return DownloadChapterImages(imageUrls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), RequestType.MangaImage, comicInfoPath, progressToken:progressToken); + return DownloadChapterImages(imageUrls.ToArray(), chapter.GetArchiveFilePath(), RequestType.MangaImage, comicInfoPath, progressToken:progressToken); } } \ No newline at end of file diff --git a/Tranga/MangaConnectors/MangaHere.cs b/Tranga/MangaConnectors/MangaHere.cs index fc9406e..0da1c5a 100644 --- a/Tranga/MangaConnectors/MangaHere.cs +++ b/Tranga/MangaConnectors/MangaHere.cs @@ -186,7 +186,7 @@ public class MangaHere : MangaConnector if (progressToken is not null) progressToken.increments = images;//we blip to normal length, in downloadchapterimages it is increasaed by the amount of urls again - return DownloadChapterImages(imageUrls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), RequestType.MangaImage, comicInfoPath, progressToken:progressToken); + return DownloadChapterImages(imageUrls.ToArray(), chapter.GetArchiveFilePath(), RequestType.MangaImage, comicInfoPath, progressToken:progressToken); } private string[] ParseImageUrlsFromHtml(HtmlDocument document) diff --git a/Tranga/MangaConnectors/MangaKatana.cs b/Tranga/MangaConnectors/MangaKatana.cs index e2284c4..64ab6c4 100644 --- a/Tranga/MangaConnectors/MangaKatana.cs +++ b/Tranga/MangaConnectors/MangaKatana.cs @@ -217,7 +217,7 @@ public class MangaKatana : MangaConnector string comicInfoPath = Path.GetTempFileName(); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); - return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), RequestType.MangaImage, comicInfoPath, "https://mangakatana.com/", progressToken:progressToken); + return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(), RequestType.MangaImage, comicInfoPath, "https://mangakatana.com/", progressToken:progressToken); } private string[] ParseImageUrlsFromHtml(string mangaUrl) diff --git a/Tranga/MangaConnectors/MangaLife.cs b/Tranga/MangaConnectors/MangaLife.cs index 43ca2e0..44b54a5 100644 --- a/Tranga/MangaConnectors/MangaLife.cs +++ b/Tranga/MangaConnectors/MangaLife.cs @@ -194,6 +194,6 @@ public class MangaLife : MangaConnector string comicInfoPath = Path.GetTempFileName(); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); - return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), RequestType.MangaImage, comicInfoPath, progressToken:progressToken); + return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(), RequestType.MangaImage, comicInfoPath, progressToken:progressToken); } } \ No newline at end of file diff --git a/Tranga/MangaConnectors/Manganato.cs b/Tranga/MangaConnectors/Manganato.cs index 1f3a0e5..c0a922a 100644 --- a/Tranga/MangaConnectors/Manganato.cs +++ b/Tranga/MangaConnectors/Manganato.cs @@ -209,7 +209,7 @@ public class Manganato : MangaConnector string comicInfoPath = Path.GetTempFileName(); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); - return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), RequestType.MangaImage, comicInfoPath, "https://chapmanganato.com/", progressToken:progressToken); + return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(), RequestType.MangaImage, comicInfoPath, "https://chapmanganato.com/", progressToken:progressToken); } private string[] ParseImageUrlsFromHtml(HtmlDocument document) diff --git a/Tranga/MangaConnectors/Mangasee.cs b/Tranga/MangaConnectors/Mangasee.cs index 1bc7445..7128910 100644 --- a/Tranga/MangaConnectors/Mangasee.cs +++ b/Tranga/MangaConnectors/Mangasee.cs @@ -225,6 +225,6 @@ public class Mangasee : MangaConnector string comicInfoPath = Path.GetTempFileName(); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); - return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), RequestType.MangaImage, comicInfoPath, progressToken:progressToken); + return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(), RequestType.MangaImage, comicInfoPath, progressToken:progressToken); } } \ No newline at end of file diff --git a/Tranga/MangaConnectors/Mangaworld.cs b/Tranga/MangaConnectors/Mangaworld.cs index d5725d7..7cbe7ef 100644 --- a/Tranga/MangaConnectors/Mangaworld.cs +++ b/Tranga/MangaConnectors/Mangaworld.cs @@ -209,7 +209,7 @@ public class Mangaworld: MangaConnector string comicInfoPath = Path.GetTempFileName(); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); - return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), RequestType.MangaImage, comicInfoPath, "https://www.mangaworld.bz/", progressToken:progressToken); + return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(), RequestType.MangaImage, comicInfoPath, "https://www.mangaworld.bz/", progressToken:progressToken); } private string[] ParseImageUrlsFromHtml(HtmlDocument document) diff --git a/Tranga/MangaConnectors/ManhuaPlus.cs b/Tranga/MangaConnectors/ManhuaPlus.cs index 3fe392c..524c267 100644 --- a/Tranga/MangaConnectors/ManhuaPlus.cs +++ b/Tranga/MangaConnectors/ManhuaPlus.cs @@ -179,6 +179,6 @@ public class ManhuaPlus : MangaConnector string comicInfoPath = Path.GetTempFileName(); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); - return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), RequestType.MangaImage, comicInfoPath, progressToken:progressToken); + return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(), RequestType.MangaImage, comicInfoPath, progressToken:progressToken); } } \ No newline at end of file diff --git a/Tranga/Server.cs b/Tranga/Server.cs index e2f19f0..95dad91 100644 --- a/Tranga/Server.cs +++ b/Tranga/Server.cs @@ -19,9 +19,9 @@ public class Server : GlobalBase { this._parent = parent; if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - this._listener.Prefixes.Add($"http://*:{settings.apiPortNumber}/"); + this._listener.Prefixes.Add($"http://*:{TrangaSettings.apiPortNumber}/"); else - this._listener.Prefixes.Add($"http://localhost:{settings.apiPortNumber}/"); + this._listener.Prefixes.Add($"http://localhost:{TrangaSettings.apiPortNumber}/"); Thread listenThread = new (Listen); listenThread.Start(); Thread watchThread = new(WatchRunning); @@ -198,16 +198,16 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.Where(jjob => jjob is DownloadNewChapters).OrderBy(jjob => ((DownloadNewChapters)jjob).manga.sortName)); break; case "Settings": - SendResponse(HttpStatusCode.OK, response, settings); + SendResponse(HttpStatusCode.OK, response, TrangaSettings.Serialize()); break; case "Settings/userAgent": - SendResponse(HttpStatusCode.OK, response, settings.userAgent); + SendResponse(HttpStatusCode.OK, response, TrangaSettings.userAgent); break; case "Settings/customRequestLimit": - SendResponse(HttpStatusCode.OK, response, settings.requestLimits); + SendResponse(HttpStatusCode.OK, response, TrangaSettings.requestLimits); break; case "Settings/AprilFoolsMode": - SendResponse(HttpStatusCode.OK, response, settings.aprilFoolsMode); + SendResponse(HttpStatusCode.OK, response, TrangaSettings.aprilFoolsMode); break; case "NotificationConnectors": SendResponse(HttpStatusCode.OK, response, notificationConnectors); @@ -314,7 +314,7 @@ public class Server : GlobalBase } if (requestVariables.TryGetValue("customFolderName", out customFolderName)) - manga.MovePublicationFolder(settings.downloadLocation, customFolderName); + manga.MovePublicationFolder(TrangaSettings.downloadLocation, customFolderName); requestVariables.TryGetValue("translatedLanguage", out translatedLanguage); _parent.jobBoss.AddJob(new DownloadNewChapters(this, connector!, manga, true, interval, translatedLanguage: translatedLanguage??"en")); @@ -343,7 +343,7 @@ public class Server : GlobalBase } if (requestVariables.TryGetValue("customFolderName", out customFolderName)) - manga.MovePublicationFolder(settings.downloadLocation, customFolderName); + manga.MovePublicationFolder(TrangaSettings.downloadLocation, customFolderName); requestVariables.TryGetValue("translatedLanguage", out translatedLanguage); _parent.jobBoss.AddJob(new DownloadNewChapters(this, connector!, manga, false, translatedLanguage: translatedLanguage??"en")); @@ -405,7 +405,7 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.BadRequest, response); break; } - settings.UpdateDownloadLocation(downloadLocation, moveFiles); + TrangaSettings.UpdateDownloadLocation(downloadLocation, moveFiles); SendResponse(HttpStatusCode.Accepted, response); break; case "Settings/AprilFoolsMode": @@ -415,7 +415,7 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.BadRequest, response); break; } - settings.UpdateAprilFoolsMode(aprilFoolsModeEnabled); + TrangaSettings.UpdateAprilFoolsMode(aprilFoolsModeEnabled); SendResponse(HttpStatusCode.Accepted, response); break; /*case "Settings/UpdateWorkingDirectory": @@ -433,11 +433,11 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.BadRequest, response); break; } - settings.UpdateUserAgent(customUserAgent); + TrangaSettings.UpdateUserAgent(customUserAgent); SendResponse(HttpStatusCode.Accepted, response); break; case "Settings/userAgent/Reset": - settings.UpdateUserAgent(null); + TrangaSettings.UpdateUserAgent(null); SendResponse(HttpStatusCode.Accepted, response); break; case "Settings/customRequestLimit": @@ -449,18 +449,12 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.BadRequest, response); break; } - - if (settings.requestLimits.ContainsKey(requestType)) - { - settings.requestLimits[requestType] = requestsPerMinute; - SendResponse(HttpStatusCode.Accepted, response); - }else - SendResponse(HttpStatusCode.BadRequest, response); - settings.ExportSettings(); + + TrangaSettings.UpdateRateLimit(requestType, requestsPerMinute); + SendResponse(HttpStatusCode.Accepted, response); break; case "Settings/customRequestLimit/Reset": - settings.requestLimits = TrangaSettings.DefaultRequestLimits; - settings.ExportSettings(); + TrangaSettings.ResetRateLimits(); break; case "NotificationConnectors/Update": if (!requestVariables.TryGetValue("notificationConnector", out notificationConnectorStr) || diff --git a/Tranga/Tranga.cs b/Tranga/Tranga.cs index 1471365..3c9a209 100644 --- a/Tranga/Tranga.cs +++ b/Tranga/Tranga.cs @@ -11,10 +11,9 @@ public partial class Tranga : GlobalBase private Server _server; private HashSet _connectors; - public Tranga(Logger? logger, TrangaSettings settings) : base(logger, settings) + public Tranga(Logger? logger) : base(logger) { Log("\n\n _______ \n|_ _|.----..---.-..-----..-----..---.-.\n | | | _|| _ || || _ || _ |\n |___| |__| |___._||__|__||___ ||___._|\n |_____| \n\n"); - Log(settings.ToString()); keepRunning = true; _connectors = new HashSet() { @@ -70,7 +69,7 @@ public partial class Tranga : GlobalBase { while (keepRunning) { - if(!settings.aprilFoolsMode || !IsAprilFirst()) + if(!TrangaSettings.aprilFoolsMode || !IsAprilFirst()) jobBoss.CheckJobs(); else Log("April Fools Mode in Effect"); diff --git a/Tranga/TrangaArgs.cs b/Tranga/TrangaArgs.cs index e95ad91..77dfea3 100644 --- a/Tranga/TrangaArgs.cs +++ b/Tranga/TrangaArgs.cs @@ -36,34 +36,14 @@ public partial class Tranga : GlobalBase enabledLoggers.Add(Logger.LoggerType.FileLogger); Logger logger = new(enabledLoggers.ToArray(), Console.Out, Console.OutputEncoding, directoryPath); - TrangaSettings? settings = null; bool dlp = fetched.TryGetValue(downloadLocation, out string[]? downloadLocationPath); - bool wdp = fetched.TryGetValue(downloadLocation, out string[]? workingDirectoryPath); + bool wdp = fetched.TryGetValue(workingDirectory, out string[]? workingDirectoryPath); - if (dlp && wdp) - { - settings = new TrangaSettings(downloadLocationPath![0], workingDirectoryPath![0]); - }else if (dlp) - { - if (settings is null) - settings = new TrangaSettings(downloadLocation: downloadLocationPath![0]); - else - settings = new TrangaSettings(downloadLocation: downloadLocationPath![0], settings.workingDirectory); - }else if (wdp) - { - if (settings is null) - settings = new TrangaSettings(downloadLocation: workingDirectoryPath![0]); - else - settings = new TrangaSettings(settings.downloadLocation, workingDirectoryPath![0]); - } - else - { - settings = new TrangaSettings(); - } - - Directory.CreateDirectory(settings.downloadLocation);//TODO validate path - Directory.CreateDirectory(settings.workingDirectory);//TODO validate path + if (wdp) + TrangaSettings.LoadFromWorkingDirectory(workingDirectoryPath![0]); + if(dlp) + TrangaSettings.CreateOrUpdate(downloadDirectory: downloadLocationPath![0]); - Tranga _ = new (logger, settings); + Tranga _ = new (logger); } } \ No newline at end of file diff --git a/Tranga/TrangaSettings.cs b/Tranga/TrangaSettings.cs index 2ad9492..e2b9390 100644 --- a/Tranga/TrangaSettings.cs +++ b/Tranga/TrangaSettings.cs @@ -1,7 +1,6 @@ using System.Runtime.InteropServices; -using System.Text.Json.Nodes; -using System.Text.RegularExpressions; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Tranga.LibraryConnectors; using Tranga.MangaConnectors; using Tranga.NotificationConnectors; @@ -9,20 +8,20 @@ using static System.IO.UnixFileMode; namespace Tranga; -public class TrangaSettings +public static class TrangaSettings { - public string downloadLocation { get; private set; } - public string workingDirectory { get; private set; } - public int apiPortNumber { get; init; } - public string userAgent { get; private set; } = DefaultUserAgent; - [JsonIgnore] public string settingsFilePath => Path.Join(workingDirectory, "settings.json"); - [JsonIgnore] public string libraryConnectorsFilePath => Path.Join(workingDirectory, "libraryConnectors.json"); - [JsonIgnore] public string notificationConnectorsFilePath => Path.Join(workingDirectory, "notificationConnectors.json"); - [JsonIgnore] public string jobsFolderPath => Path.Join(workingDirectory, "jobs"); - [JsonIgnore] public string coverImageCache => Path.Join(workingDirectory, "imageCache"); [JsonIgnore] internal static readonly string DefaultUserAgent = $"Tranga ({Enum.GetName(Environment.OSVersion.Platform)}; {(Environment.Is64BitOperatingSystem ? "x64" : "")}) / 1.0"; - public ushort? version { get; } = 2; - public bool aprilFoolsMode { get; private set; } = true; + public static string downloadLocation { get; private set; } = (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/Manga" : Path.Join(Directory.GetCurrentDirectory(), "Downloads")); + public static string workingDirectory { get; private set; } = Path.Join(RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/usr/share" : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "tranga-api"); + public static int apiPortNumber { get; private set; } = 6531; + public static string userAgent { get; private set; } = DefaultUserAgent; + [JsonIgnore] public static string settingsFilePath => Path.Join(workingDirectory, "settings.json"); + [JsonIgnore] public static string libraryConnectorsFilePath => Path.Join(workingDirectory, "libraryConnectors.json"); + [JsonIgnore] public static string notificationConnectorsFilePath => Path.Join(workingDirectory, "notificationConnectors.json"); + [JsonIgnore] public static string jobsFolderPath => Path.Join(workingDirectory, "jobs"); + [JsonIgnore] public static string coverImageCache => Path.Join(workingDirectory, "imageCache"); + public static ushort? version { get; } = 2; + public static bool aprilFoolsMode { get; private set; } = true; [JsonIgnore]internal static readonly Dictionary DefaultRequestLimits = new () { {RequestType.MangaInfo, 250}, @@ -33,50 +32,38 @@ public class TrangaSettings {RequestType.Default, 60} }; - public Dictionary requestLimits { get; set; } = DefaultRequestLimits; + public static Dictionary requestLimits { get; set; } = DefaultRequestLimits; - public TrangaSettings(string? downloadLocation = null, string? workingDirectory = null, int? apiPortNumber = null) + public static void LoadFromWorkingDirectory(string directory) { - string wd = workingDirectory ?? Path.Join(RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/usr/share" : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "tranga-api"); - string sfp = Path.Join(wd, "settings.json"); - - string lockFilePath = $"{sfp}.lock"; - if (File.Exists(sfp) && !File.Exists(lockFilePath)) - {//Load from settings file - FileStream lockFile = File.Create(lockFilePath,0, FileOptions.DeleteOnClose); //lock settingsfile - string settingsStr = File.ReadAllText(sfp); - settingsStr = Regex.Replace(settingsStr, @"""MangaDexAuthor"": [0-9]+,", "");//https://github.com/C9Glax/tranga/pull/161 Remove sometime in the future :3 - TrangaSettings settings = JsonConvert.DeserializeObject(settingsStr)!; - this.requestLimits = settings.requestLimits; - this.userAgent = settings.userAgent; - this.downloadLocation = downloadLocation ?? settings.downloadLocation; - this.workingDirectory = workingDirectory ?? settings.workingDirectory; - this.apiPortNumber = apiPortNumber ?? settings.apiPortNumber; - lockFile.Close(); //unlock settingsfile - } - else if(!File.Exists(sfp)) - {//No settings file exists - if (downloadLocation?.Length < 1 || workingDirectory?.Length < 1) - throw new ArgumentException("Download-location and working-directory paths can not be empty!"); - this.requestLimits = DefaultRequestLimits; - this.userAgent = DefaultUserAgent; - this.apiPortNumber = apiPortNumber ?? 6531; - this.downloadLocation = downloadLocation ?? (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/Manga" : Path.Join(Directory.GetCurrentDirectory(), "Downloads")); - this.workingDirectory = workingDirectory ?? Path.Join(RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/usr/share" : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "tranga-api"); - ExportSettings(); + TrangaSettings.workingDirectory = directory; + if (!File.Exists(settingsFilePath)) + { + return; } else - {//Settingsfile is locked - this.requestLimits = DefaultRequestLimits; - this.userAgent = DefaultUserAgent; - this.apiPortNumber = apiPortNumber!.Value; - this.downloadLocation = downloadLocation!; - this.workingDirectory = workingDirectory!; + { + Deserialize(File.ReadAllText(settingsFilePath)); } - UpdateDownloadLocation(this.downloadLocation, false); + + Directory.CreateDirectory(downloadLocation); + Directory.CreateDirectory(workingDirectory); + ExportSettings(); } - public HashSet LoadLibraryConnectors(GlobalBase clone) + public static void CreateOrUpdate(string? downloadDirectory = null, string? pWorkingDirectory = null, int? pApiPortNumber = null, string? pUserAgent = null, bool? pAprilFoolsMode = null) + { + TrangaSettings.downloadLocation = downloadDirectory ?? TrangaSettings.downloadLocation; + TrangaSettings.workingDirectory = pWorkingDirectory ?? TrangaSettings.workingDirectory; + TrangaSettings.apiPortNumber = pApiPortNumber ?? TrangaSettings.apiPortNumber; + TrangaSettings.userAgent = pUserAgent ?? TrangaSettings.userAgent; + TrangaSettings.aprilFoolsMode = pAprilFoolsMode ?? TrangaSettings.aprilFoolsMode; + Directory.CreateDirectory(downloadLocation); + Directory.CreateDirectory(workingDirectory); + ExportSettings(); + } + + public static HashSet LoadLibraryConnectors(GlobalBase clone) { if (!File.Exists(libraryConnectorsFilePath)) return new HashSet(); @@ -90,7 +77,7 @@ public class TrangaSettings })!; } - public HashSet LoadNotificationConnectors(GlobalBase clone) + public static HashSet LoadNotificationConnectors(GlobalBase clone) { if (!File.Exists(notificationConnectorsFilePath)) return new HashSet(); @@ -104,13 +91,13 @@ public class TrangaSettings })!; } - public void UpdateAprilFoolsMode(bool enabled) + public static void UpdateAprilFoolsMode(bool enabled) { - this.aprilFoolsMode = enabled; + TrangaSettings.aprilFoolsMode = enabled; ExportSettings(); } - public void UpdateDownloadLocation(string newPath, bool moveFiles = true) + public static void UpdateDownloadLocation(string newPath, bool moveFiles = true) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) Directory.CreateDirectory(newPath, @@ -118,32 +105,44 @@ public class TrangaSettings else Directory.CreateDirectory(newPath); - if (moveFiles && Directory.Exists(this.downloadLocation)) - Directory.Move(this.downloadLocation, newPath); + if (moveFiles && Directory.Exists(TrangaSettings.downloadLocation)) + Directory.Move(TrangaSettings.downloadLocation, newPath); - this.downloadLocation = newPath; + TrangaSettings.downloadLocation = newPath; ExportSettings(); } - public void UpdateWorkingDirectory(string newPath) + public static void UpdateWorkingDirectory(string newPath) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) Directory.CreateDirectory(newPath, GroupRead | GroupWrite | None | OtherRead | OtherWrite | UserRead | UserWrite); else Directory.CreateDirectory(newPath); - Directory.Move(this.workingDirectory, newPath); - this.workingDirectory = newPath; + Directory.Move(TrangaSettings.workingDirectory, newPath); + TrangaSettings.workingDirectory = newPath; ExportSettings(); } - public void UpdateUserAgent(string? customUserAgent) + public static void UpdateUserAgent(string? customUserAgent) { - this.userAgent = customUserAgent ?? DefaultUserAgent; + TrangaSettings.userAgent = customUserAgent ?? DefaultUserAgent; ExportSettings(); } - public void ExportSettings() + public static void UpdateRateLimit(RequestType requestType, int newLimit) + { + TrangaSettings.requestLimits[requestType] = newLimit; + ExportSettings(); + } + + public static void ResetRateLimits() + { + TrangaSettings.requestLimits = DefaultRequestLimits; + ExportSettings(); + } + + public static void ExportSettings() { if (File.Exists(settingsFilePath)) { @@ -152,22 +151,36 @@ public class TrangaSettings } else Directory.CreateDirectory(new FileInfo(settingsFilePath).DirectoryName!); - File.WriteAllText(settingsFilePath, JsonConvert.SerializeObject(this, Formatting.Indented)); + File.WriteAllText(settingsFilePath, Serialize()); } - public string GetFullCoverPath(Manga manga) + public static string Serialize() { - return Path.Join(this.coverImageCache, manga.coverFileNameInCache); + JObject jobj = new JObject(); + jobj.Add("downloadLocation", JToken.FromObject(TrangaSettings.downloadLocation)); + jobj.Add("workingDirectory", JToken.FromObject(TrangaSettings.workingDirectory)); + jobj.Add("apiPortNumber", JToken.FromObject(TrangaSettings.apiPortNumber)); + jobj.Add("userAgent", JToken.FromObject(TrangaSettings.userAgent)); + jobj.Add("aprilFoolsMode", JToken.FromObject(TrangaSettings.aprilFoolsMode)); + jobj.Add("version", JToken.FromObject(TrangaSettings.version)); + jobj.Add("requestLimits", JToken.FromObject(TrangaSettings.requestLimits)); + return jobj.ToString(); } - public override string ToString() + public static void Deserialize(string serialized) { - return $"TrangaSettings:\n" + - $"\tDownloadLocation: {downloadLocation}\n" + - $"\tworkingDirectory: {workingDirectory}\n" + - $"\tjobsFolderPath: {jobsFolderPath}\n" + - $"\tsettingsFilePath: {settingsFilePath}\n" + - $"\t\tnotificationConnectors: {notificationConnectorsFilePath}\n" + - $"\t\tlibraryConnectors: {libraryConnectorsFilePath}\n"; + JObject jobj = JObject.Parse(serialized); + if (jobj.TryGetValue("downloadLocation", out JToken? dl)) + TrangaSettings.downloadLocation = dl.Value()!; + if (jobj.TryGetValue("workingDirectory", out JToken? wd)) + TrangaSettings.workingDirectory = wd.Value()!; + if (jobj.TryGetValue("apiPortNumber", out JToken? apn)) + TrangaSettings.apiPortNumber = apn.Value(); + if (jobj.TryGetValue("userAgent", out JToken? ua)) + TrangaSettings.userAgent = ua.Value()!; + if (jobj.TryGetValue("aprilFoolsMode", out JToken? afm)) + TrangaSettings.aprilFoolsMode = afm.Value()!; + if (jobj.TryGetValue("requestLimits", out JToken? rl)) + TrangaSettings.requestLimits = rl.ToObject>()!; } } \ No newline at end of file From 6f3bba99b0f9963b08bca80cda2bf86c50783395 Mon Sep 17 00:00:00 2001 From: Glax Date: Mon, 26 Aug 2024 12:59:19 +0200 Subject: [PATCH 13/22] Fix Settings not returning as JSON --- Tranga/Server.cs | 2 +- Tranga/TrangaSettings.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Tranga/Server.cs b/Tranga/Server.cs index 935d466..8656758 100644 --- a/Tranga/Server.cs +++ b/Tranga/Server.cs @@ -198,7 +198,7 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.Where(jjob => jjob is DownloadNewChapters).OrderBy(jjob => ((DownloadNewChapters)jjob).manga.sortName)); break; case "Settings": - SendResponse(HttpStatusCode.OK, response, TrangaSettings.Serialize()); + SendResponse(HttpStatusCode.OK, response, TrangaSettings.AsJObject()); break; case "Settings/userAgent": SendResponse(HttpStatusCode.OK, response, TrangaSettings.userAgent); diff --git a/Tranga/TrangaSettings.cs b/Tranga/TrangaSettings.cs index e2b9390..ae5b91d 100644 --- a/Tranga/TrangaSettings.cs +++ b/Tranga/TrangaSettings.cs @@ -154,7 +154,7 @@ public static class TrangaSettings File.WriteAllText(settingsFilePath, Serialize()); } - public static string Serialize() + public static JObject AsJObject() { JObject jobj = new JObject(); jobj.Add("downloadLocation", JToken.FromObject(TrangaSettings.downloadLocation)); @@ -164,9 +164,11 @@ public static class TrangaSettings jobj.Add("aprilFoolsMode", JToken.FromObject(TrangaSettings.aprilFoolsMode)); jobj.Add("version", JToken.FromObject(TrangaSettings.version)); jobj.Add("requestLimits", JToken.FromObject(TrangaSettings.requestLimits)); - return jobj.ToString(); + return jobj; } + public static string Serialize() => AsJObject().ToString(); + public static void Deserialize(string serialized) { JObject jobj = JObject.Parse(serialized); From 14e33cc49687074ffa762566e879a886392971cc Mon Sep 17 00:00:00 2001 From: Glax Date: Mon, 26 Aug 2024 13:09:33 +0200 Subject: [PATCH 14/22] Fix Settings not loading on reload --- CLI/Program.cs | 2 ++ Tranga/TrangaArgs.cs | 2 ++ Tranga/TrangaSettings.cs | 11 ++++------- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CLI/Program.cs b/CLI/Program.cs index f927ea2..c349b33 100644 --- a/CLI/Program.cs +++ b/CLI/Program.cs @@ -49,6 +49,8 @@ internal sealed class TrangaCli : Command if(settings.workingDirectory is not null) TrangaSettings.LoadFromWorkingDirectory(settings.workingDirectory); + else + TrangaSettings.CreateOrUpdate(); if(settings.downloadLocation is not null) TrangaSettings.CreateOrUpdate(downloadDirectory: settings.downloadLocation); diff --git a/Tranga/TrangaArgs.cs b/Tranga/TrangaArgs.cs index 77dfea3..f643d4a 100644 --- a/Tranga/TrangaArgs.cs +++ b/Tranga/TrangaArgs.cs @@ -41,6 +41,8 @@ public partial class Tranga : GlobalBase if (wdp) TrangaSettings.LoadFromWorkingDirectory(workingDirectoryPath![0]); + else + TrangaSettings.CreateOrUpdate(); if(dlp) TrangaSettings.CreateOrUpdate(downloadDirectory: downloadLocationPath![0]); diff --git a/Tranga/TrangaSettings.cs b/Tranga/TrangaSettings.cs index ae5b91d..b945fe5 100644 --- a/Tranga/TrangaSettings.cs +++ b/Tranga/TrangaSettings.cs @@ -37,14 +37,9 @@ public static class TrangaSettings public static void LoadFromWorkingDirectory(string directory) { TrangaSettings.workingDirectory = directory; - if (!File.Exists(settingsFilePath)) - { - return; - } - else - { + if(File.Exists(settingsFilePath)) Deserialize(File.ReadAllText(settingsFilePath)); - } + else return; Directory.CreateDirectory(downloadLocation); Directory.CreateDirectory(workingDirectory); @@ -53,6 +48,8 @@ public static class TrangaSettings public static void CreateOrUpdate(string? downloadDirectory = null, string? pWorkingDirectory = null, int? pApiPortNumber = null, string? pUserAgent = null, bool? pAprilFoolsMode = null) { + if(pWorkingDirectory is null && File.Exists(settingsFilePath)) + LoadFromWorkingDirectory(workingDirectory); TrangaSettings.downloadLocation = downloadDirectory ?? TrangaSettings.downloadLocation; TrangaSettings.workingDirectory = pWorkingDirectory ?? TrangaSettings.workingDirectory; TrangaSettings.apiPortNumber = pApiPortNumber ?? TrangaSettings.apiPortNumber; From 7b91bb699f8e21ce14545101e3587a9ac60e1b4f Mon Sep 17 00:00:00 2001 From: Glax Date: Mon, 26 Aug 2024 13:09:33 +0200 Subject: [PATCH 15/22] Fix Settings not loading on reload --- CLI/Program.cs | 2 ++ Tranga/TrangaArgs.cs | 2 ++ Tranga/TrangaSettings.cs | 11 ++++------- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CLI/Program.cs b/CLI/Program.cs index f927ea2..c349b33 100644 --- a/CLI/Program.cs +++ b/CLI/Program.cs @@ -49,6 +49,8 @@ internal sealed class TrangaCli : Command if(settings.workingDirectory is not null) TrangaSettings.LoadFromWorkingDirectory(settings.workingDirectory); + else + TrangaSettings.CreateOrUpdate(); if(settings.downloadLocation is not null) TrangaSettings.CreateOrUpdate(downloadDirectory: settings.downloadLocation); diff --git a/Tranga/TrangaArgs.cs b/Tranga/TrangaArgs.cs index 77dfea3..f643d4a 100644 --- a/Tranga/TrangaArgs.cs +++ b/Tranga/TrangaArgs.cs @@ -41,6 +41,8 @@ public partial class Tranga : GlobalBase if (wdp) TrangaSettings.LoadFromWorkingDirectory(workingDirectoryPath![0]); + else + TrangaSettings.CreateOrUpdate(); if(dlp) TrangaSettings.CreateOrUpdate(downloadDirectory: downloadLocationPath![0]); diff --git a/Tranga/TrangaSettings.cs b/Tranga/TrangaSettings.cs index e2b9390..ec44ecc 100644 --- a/Tranga/TrangaSettings.cs +++ b/Tranga/TrangaSettings.cs @@ -37,14 +37,9 @@ public static class TrangaSettings public static void LoadFromWorkingDirectory(string directory) { TrangaSettings.workingDirectory = directory; - if (!File.Exists(settingsFilePath)) - { - return; - } - else - { + if(File.Exists(settingsFilePath)) Deserialize(File.ReadAllText(settingsFilePath)); - } + else return; Directory.CreateDirectory(downloadLocation); Directory.CreateDirectory(workingDirectory); @@ -53,6 +48,8 @@ public static class TrangaSettings public static void CreateOrUpdate(string? downloadDirectory = null, string? pWorkingDirectory = null, int? pApiPortNumber = null, string? pUserAgent = null, bool? pAprilFoolsMode = null) { + if(pWorkingDirectory is null && File.Exists(settingsFilePath)) + LoadFromWorkingDirectory(workingDirectory); TrangaSettings.downloadLocation = downloadDirectory ?? TrangaSettings.downloadLocation; TrangaSettings.workingDirectory = pWorkingDirectory ?? TrangaSettings.workingDirectory; TrangaSettings.apiPortNumber = pApiPortNumber ?? TrangaSettings.apiPortNumber; From 6d723b6355c84d39ea4f454a7ce05315bbebd42c Mon Sep 17 00:00:00 2001 From: Glax Date: Mon, 26 Aug 2024 12:59:19 +0200 Subject: [PATCH 16/22] Fix Settings not returning as JSON --- Tranga/Server.cs | 2 +- Tranga/TrangaSettings.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Tranga/Server.cs b/Tranga/Server.cs index 95dad91..8ed2e2c 100644 --- a/Tranga/Server.cs +++ b/Tranga/Server.cs @@ -198,7 +198,7 @@ public class Server : GlobalBase SendResponse(HttpStatusCode.OK, response, _parent.jobBoss.jobs.Where(jjob => jjob is DownloadNewChapters).OrderBy(jjob => ((DownloadNewChapters)jjob).manga.sortName)); break; case "Settings": - SendResponse(HttpStatusCode.OK, response, TrangaSettings.Serialize()); + SendResponse(HttpStatusCode.OK, response, TrangaSettings.AsJObject()); break; case "Settings/userAgent": SendResponse(HttpStatusCode.OK, response, TrangaSettings.userAgent); diff --git a/Tranga/TrangaSettings.cs b/Tranga/TrangaSettings.cs index ec44ecc..b945fe5 100644 --- a/Tranga/TrangaSettings.cs +++ b/Tranga/TrangaSettings.cs @@ -151,7 +151,7 @@ public static class TrangaSettings File.WriteAllText(settingsFilePath, Serialize()); } - public static string Serialize() + public static JObject AsJObject() { JObject jobj = new JObject(); jobj.Add("downloadLocation", JToken.FromObject(TrangaSettings.downloadLocation)); @@ -161,9 +161,11 @@ public static class TrangaSettings jobj.Add("aprilFoolsMode", JToken.FromObject(TrangaSettings.aprilFoolsMode)); jobj.Add("version", JToken.FromObject(TrangaSettings.version)); jobj.Add("requestLimits", JToken.FromObject(TrangaSettings.requestLimits)); - return jobj.ToString(); + return jobj; } + public static string Serialize() => AsJObject().ToString(); + public static void Deserialize(string serialized) { JObject jobj = JObject.Parse(serialized); From 1bd20791b85bfc255457e2dc22f6f60ff943b859 Mon Sep 17 00:00:00 2001 From: Glax Date: Mon, 26 Aug 2024 13:18:48 +0200 Subject: [PATCH 17/22] Add Cache-Control headers --- Tranga/Server.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tranga/Server.cs b/Tranga/Server.cs index 8ed2e2c..78f96de 100644 --- a/Tranga/Server.cs +++ b/Tranga/Server.cs @@ -716,6 +716,7 @@ public class Server : GlobalBase if (content is not Stream) { response.ContentType = "application/json"; + response.AddHeader("Cache-Control", "no-store"); try { response.OutputStream.Write(content is not null @@ -731,6 +732,7 @@ public class Server : GlobalBase else if(content is FileStream stream) { string contentType = stream.Name.Split('.')[^1]; + response.AddHeader("Cache-Control", "max-age=600"); switch (contentType.ToLower()) { case "gif": From 8f51d223034c27e6f0e9d9ac7ed7e281472cc246 Mon Sep 17 00:00:00 2001 From: Glax Date: Mon, 26 Aug 2024 13:21:34 +0200 Subject: [PATCH 18/22] Fix try-block in Server.cs --- Tranga/Server.cs | 57 ++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/Tranga/Server.cs b/Tranga/Server.cs index 78f96de..4e387f1 100644 --- a/Tranga/Server.cs +++ b/Tranga/Server.cs @@ -713,45 +713,46 @@ public class Server : GlobalBase response.AddHeader("Access-Control-Max-Age", "1728000"); response.AppendHeader("Access-Control-Allow-Origin", "*"); - if (content is not Stream) + try { - response.ContentType = "application/json"; - response.AddHeader("Cache-Control", "no-store"); - try + if (content is not Stream) { + response.ContentType = "application/json"; + response.AddHeader("Cache-Control", "no-store"); response.OutputStream.Write(content is not null ? Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(content)) : Array.Empty()); response.OutputStream.Close(); } - catch (HttpListenerException e) + else if (content is FileStream stream) { - Log(e.ToString()); + string contentType = stream.Name.Split('.')[^1]; + response.AddHeader("Cache-Control", "max-age=600"); + switch (contentType.ToLower()) + { + case "gif": + response.ContentType = "image/gif"; + break; + case "png": + response.ContentType = "image/png"; + break; + case "jpg": + case "jpeg": + response.ContentType = "image/jpeg"; + break; + case "log": + response.ContentType = "text/plain"; + break; + } + + stream.CopyTo(response.OutputStream); + response.OutputStream.Close(); + stream.Close(); } } - else if(content is FileStream stream) + catch (HttpListenerException e) { - string contentType = stream.Name.Split('.')[^1]; - response.AddHeader("Cache-Control", "max-age=600"); - switch (contentType.ToLower()) - { - case "gif": - response.ContentType = "image/gif"; - break; - case "png": - response.ContentType = "image/png"; - break; - case "jpg": - case "jpeg": - response.ContentType = "image/jpeg"; - break; - case "log": - response.ContentType = "text/plain"; - break; - } - stream.CopyTo(response.OutputStream); - response.OutputStream.Close(); - stream.Close(); + Log(e.ToString()); } } } \ No newline at end of file From dfa8e66f3436327771dd7460c2d05370cafebdfb Mon Sep 17 00:00:00 2001 From: Glax Date: Mon, 26 Aug 2024 13:21:34 +0200 Subject: [PATCH 19/22] Fix try-block in Server.cs --- Tranga/Server.cs | 57 ++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/Tranga/Server.cs b/Tranga/Server.cs index 78f96de..4e387f1 100644 --- a/Tranga/Server.cs +++ b/Tranga/Server.cs @@ -713,45 +713,46 @@ public class Server : GlobalBase response.AddHeader("Access-Control-Max-Age", "1728000"); response.AppendHeader("Access-Control-Allow-Origin", "*"); - if (content is not Stream) + try { - response.ContentType = "application/json"; - response.AddHeader("Cache-Control", "no-store"); - try + if (content is not Stream) { + response.ContentType = "application/json"; + response.AddHeader("Cache-Control", "no-store"); response.OutputStream.Write(content is not null ? Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(content)) : Array.Empty()); response.OutputStream.Close(); } - catch (HttpListenerException e) + else if (content is FileStream stream) { - Log(e.ToString()); + string contentType = stream.Name.Split('.')[^1]; + response.AddHeader("Cache-Control", "max-age=600"); + switch (contentType.ToLower()) + { + case "gif": + response.ContentType = "image/gif"; + break; + case "png": + response.ContentType = "image/png"; + break; + case "jpg": + case "jpeg": + response.ContentType = "image/jpeg"; + break; + case "log": + response.ContentType = "text/plain"; + break; + } + + stream.CopyTo(response.OutputStream); + response.OutputStream.Close(); + stream.Close(); } } - else if(content is FileStream stream) + catch (HttpListenerException e) { - string contentType = stream.Name.Split('.')[^1]; - response.AddHeader("Cache-Control", "max-age=600"); - switch (contentType.ToLower()) - { - case "gif": - response.ContentType = "image/gif"; - break; - case "png": - response.ContentType = "image/png"; - break; - case "jpg": - case "jpeg": - response.ContentType = "image/jpeg"; - break; - case "log": - response.ContentType = "text/plain"; - break; - } - stream.CopyTo(response.OutputStream); - response.OutputStream.Close(); - stream.Close(); + Log(e.ToString()); } } } \ No newline at end of file From 9f178821b62e40b148048ec9ceb9a707fa73ff73 Mon Sep 17 00:00:00 2001 From: Glax Date: Mon, 26 Aug 2024 20:18:35 +0200 Subject: [PATCH 20/22] Fix #223 Manganato chapter relative dates. --- Tranga/MangaConnectors/Manganato.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Tranga/MangaConnectors/Manganato.cs b/Tranga/MangaConnectors/Manganato.cs index c0a922a..d03d4dd 100644 --- a/Tranga/MangaConnectors/Manganato.cs +++ b/Tranga/MangaConnectors/Manganato.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.Globalization; +using System.Net; using System.Text.RegularExpressions; using HtmlAgilityPack; using Tranga.Jobs; @@ -126,10 +127,17 @@ public class Manganato : MangaConnector .InnerText.Replace("Description :", ""); while (description.StartsWith('\n')) description = description.Substring(1); + + string pattern = "MMM dd,yyyy HH:mm"; - string yearString = document.DocumentNode.Descendants("li").Last(li => li.HasClass("a-h")).Descendants("span") - .First(s => s.HasClass("chapter-time")).InnerText; - int year = Convert.ToInt32(yearString.Split(',')[^1]) + 2000; + HtmlNode oldestChapter = document.DocumentNode + .SelectNodes("//chapter-time[contains(concat(' ',normalize-space(@class),' '),' chapter-time ')]").MaxBy( + node => DateTime.ParseExact(node.GetAttributeValue("title", "Dec 31 2400, 23:59"), pattern, + CultureInfo.InvariantCulture))!; + + + int year = DateTime.ParseExact(oldestChapter.GetAttributeValue("title", "Dec 31 2400, 23:59"), pattern, + CultureInfo.InvariantCulture).Year; Manga manga = new (sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links, year, originalLanguage, publicationId, releaseStatus, websiteUrl: websiteUrl); From e0a7d1a18794597bd1817f2598a97867f1d0f942 Mon Sep 17 00:00:00 2001 From: Glax Date: Mon, 26 Aug 2024 20:28:40 +0200 Subject: [PATCH 21/22] Fix #220 Mangaworld Chapter number parsing --- Tranga/MangaConnectors/Mangaworld.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tranga/MangaConnectors/Mangaworld.cs b/Tranga/MangaConnectors/Mangaworld.cs index 7cbe7ef..1140468 100644 --- a/Tranga/MangaConnectors/Mangaworld.cs +++ b/Tranga/MangaConnectors/Mangaworld.cs @@ -169,7 +169,8 @@ public class Mangaworld: MangaConnector { foreach (HtmlNode chNode in chaptersWrapper.SelectNodes("div").Where(node => node.HasClass("chapter"))) { - string number = chNode.SelectSingleNode("a").SelectSingleNode("span").InnerText.Split(" ")[^1]; + string number = Regex.Match(chNode.SelectSingleNode("a").SelectSingleNode("span").InnerText, + @"[Cc]apitolo ([0-9]+).*").Groups[1].Value; string url = chNode.SelectSingleNode("a").GetAttributeValue("href", ""); ret.Add(new Chapter(manga, null, null, number, url)); } From 3122aa32e845b15ed44e41b2a4f7a9427ea67520 Mon Sep 17 00:00:00 2001 From: Glax Date: Mon, 26 Aug 2024 20:46:50 +0200 Subject: [PATCH 22/22] fix #223 wrong selector --- Tranga/MangaConnectors/Manganato.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tranga/MangaConnectors/Manganato.cs b/Tranga/MangaConnectors/Manganato.cs index d03d4dd..0c6e85b 100644 --- a/Tranga/MangaConnectors/Manganato.cs +++ b/Tranga/MangaConnectors/Manganato.cs @@ -131,9 +131,9 @@ public class Manganato : MangaConnector string pattern = "MMM dd,yyyy HH:mm"; HtmlNode oldestChapter = document.DocumentNode - .SelectNodes("//chapter-time[contains(concat(' ',normalize-space(@class),' '),' chapter-time ')]").MaxBy( + .SelectNodes("//span[contains(concat(' ',normalize-space(@class),' '),' chapter-time ')]").MaxBy( node => DateTime.ParseExact(node.GetAttributeValue("title", "Dec 31 2400, 23:59"), pattern, - CultureInfo.InvariantCulture))!; + CultureInfo.InvariantCulture).Millisecond)!; int year = DateTime.ParseExact(oldestChapter.GetAttributeValue("title", "Dec 31 2400, 23:59"), pattern,