diff --git a/Tranga.sln.DotSettings b/Tranga.sln.DotSettings index 4d3085b..4f84134 100644 --- a/Tranga.sln.DotSettings +++ b/Tranga.sln.DotSettings @@ -1,5 +1,6 @@  True True + True True True \ No newline at end of file diff --git a/Tranga/Connectors/Mangasee.cs b/Tranga/Connectors/Mangasee.cs new file mode 100644 index 0000000..bc362c9 --- /dev/null +++ b/Tranga/Connectors/Mangasee.cs @@ -0,0 +1,219 @@ +using System.Net; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using HtmlAgilityPack; +using Logging; +using Newtonsoft.Json; +using PuppeteerSharp; +using Tranga.TrangaTasks; + +namespace Tranga.Connectors; + +public class Mangasee : Connector +{ + public override string name { get; } + private IBrowser? browser = null; + + public Mangasee(string downloadLocation, string imageCachePath, Logger? logger) : base(downloadLocation, + imageCachePath, logger) + { + this.name = "Mangasee"; + this.downloadClient = new DownloadClient(new Dictionary() + { + { (byte)1, 60 } + }, logger); + + Task d = new Task(DownloadBrowser); + d.Start(); + } + + private async void DownloadBrowser() + { + logger?.WriteLine(this.GetType().ToString(), "Downloading headless browser"); + BrowserFetcher browserFetcher = new BrowserFetcher(); + double last = 0; + browserFetcher.DownloadProgressChanged += async (sender, args) => + { + double current = Convert.ToDouble(args.BytesReceived) / Convert.ToDouble(args.TotalBytesToReceive); + if (args.TotalBytesToReceive == args.BytesReceived) + { + logger?.WriteLine(this.GetType().ToString(), "Browser downloaded. Launching..."); + } + else if (current > last + 0.01) + { + logger?.WriteLine(this.GetType().ToString(), $"Browser progress: {current:P2}"); + last = current; + } + + }; + if (!browserFetcher.CanDownloadAsync(BrowserFetcher.DefaultChromiumRevision).Result) + { + logger?.WriteLine(this.GetType().ToString(), "Can't download"); + return; + } + await browserFetcher.DownloadAsync(BrowserFetcher.DefaultChromiumRevision); + this.browser = await Puppeteer.LaunchAsync(new LaunchOptions + { + Headless = true, + ExecutablePath = browserFetcher.GetExecutablePath(BrowserFetcher.DefaultChromiumRevision) + }); + } + + public override Publication[] GetPublications(string publicationTitle = "") + { + logger?.WriteLine(this.GetType().ToString(), $"Getting Publications (title={publicationTitle})"); + string sanitizedTitle = string.Concat(Regex.Matches(publicationTitle, "[A-z]* *")).ToLower().Replace(' ', '+'); + string requestUrl = $"https://mangasee123.com/_search.php"; + DownloadClient.RequestResult requestResult = + downloadClient.MakeRequest(requestUrl, (byte)1); + if (requestResult.statusCode != HttpStatusCode.OK) + return Array.Empty(); + + return ParsePublicationsFromHtml(requestResult.result, publicationTitle); + } + + private Publication[] ParsePublicationsFromHtml(Stream html, string publicationTitle) + { + string jsonString = new StreamReader(html).ReadToEnd(); + List result = JsonConvert.DeserializeObject>(jsonString)!; + Dictionary queryFiltered = new(); + foreach (SearchResultItem resultItem in result) + { + foreach (string term in publicationTitle.Split(' ')) + if (resultItem.i.Contains(term, StringComparison.CurrentCultureIgnoreCase)) + if (!queryFiltered.TryAdd(resultItem, 0)) + queryFiltered[resultItem]++; + } + + queryFiltered = queryFiltered.Where(item => item.Value >= publicationTitle.Split(' ').Length - 1) + .ToDictionary(item => item.Key, item => item.Value); + + HashSet ret = new(); + List orderedFiltered = + queryFiltered.OrderBy(item => item.Value).ToDictionary(item => item.Key, item => item.Value).Keys.ToList(); + + foreach (SearchResultItem orderedItem in orderedFiltered) + { + DownloadClient.RequestResult requestResult = + downloadClient.MakeRequest($"https://mangasee123.com/manga/{orderedItem.i}", (byte)1); + if (requestResult.statusCode != HttpStatusCode.OK) + return Array.Empty(); + ret.Add(ParseSinglePublicationFromHtml(requestResult.result, orderedItem.s, orderedItem.i, orderedItem.a)); + } + return ret.ToArray(); + } + + + private Publication ParseSinglePublicationFromHtml(Stream html, string sortName, string publicationId, string[] a) + { + StreamReader reader = new (html); + string htmlString = reader.ReadToEnd(); + HtmlDocument document = new (); + document.LoadHtml(htmlString); + + string originalLanguage = "", status = ""; + Dictionary altTitles = new(), links = new(); + HashSet tags = new(); + + HtmlNode posterNode = + document.DocumentNode.Descendants("img").First(img => img.HasClass("img-fluid") && img.HasClass("bottom-5")); + string posterUrl = posterNode.GetAttributeValue("src", ""); + string coverFileNameInCache = SaveCoverImageToCache(posterUrl, 1); + + HtmlNode attributes = document.DocumentNode.Descendants("div") + .First(div => div.HasClass("col-md-9") && div.HasClass("col-sm-8") && div.HasClass("top-5")) + .Descendants("ul").First(); + + HtmlNode[] authorsNodes = attributes.Descendants("li") + .First(node => node.InnerText.Contains("author(s):", StringComparison.CurrentCultureIgnoreCase)) + .Descendants("a").ToArray(); + string[] authors = new string[authorsNodes.Length]; + for (int j = 0; j < authors.Length; j++) + authors[j] = authorsNodes[j].InnerText; + string author = string.Join(" - ", authors); + + HtmlNode[] genreNodes = attributes.Descendants("li") + .First(node => node.InnerText.Contains("genre(s):", StringComparison.CurrentCultureIgnoreCase)) + .Descendants("a").ToArray(); + foreach (HtmlNode genreNode in genreNodes) + tags.Add(genreNode.InnerText); + + HtmlNode yearNode = attributes.Descendants("li") + .First(node => node.InnerText.Contains("released:", StringComparison.CurrentCultureIgnoreCase)) + .Descendants("a").First(); + int year = Convert.ToInt32(yearNode.InnerText); + + HtmlNode[] statusNodes = attributes.Descendants("li") + .First(node => node.InnerText.Contains("status:", StringComparison.CurrentCultureIgnoreCase)) + .Descendants("a").ToArray(); + foreach(HtmlNode statusNode in statusNodes) + if (statusNode.InnerText.Contains("publish", StringComparison.CurrentCultureIgnoreCase)) + status = statusNode.InnerText.Split(' ')[0]; + + HtmlNode descriptionNode = attributes.Descendants("li").First(node => node.InnerText.Contains("description:", StringComparison.CurrentCultureIgnoreCase)).Descendants("div").First(); + string description = descriptionNode.InnerText; + + int i = 0; + foreach(string at in a) + altTitles.Add((i++).ToString(), at); + + return new Publication(sortName, author, description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links, + year, originalLanguage, status, publicationId); + } + + private class SearchResultItem + { + public string i { get; set; } + public string s { get; set; } + public string[] a { get; set; } + } + + public override Chapter[] GetChapters(Publication publication, string language = "") + { + XDocument doc = XDocument.Load($"https://mangasee123.com/rss/{publication.publicationId}.xml"); + XElement[] chapterItems = doc.Descendants("item").ToArray(); + List ret = new(); + foreach (XElement chapter in chapterItems) + { + string? volumeNumber = null; + string chapterName = chapter.Descendants("title").First().Value; + string parseFrom = name.Replace(publication.sortName, "", StringComparison.InvariantCultureIgnoreCase); + string chapterNumber = Regex.Matches(chapterName, "[0-9]+")[^1].ToString(); + + string url = chapter.Descendants("link").First().Value; + url = url.Replace(Regex.Matches(url,"(-page-[0-9])")[0].ToString(),""); + ret.Add(new Chapter(chapterName, volumeNumber, chapterNumber, url)); + } + + ret.Reverse(); + return ret.ToArray(); + } + + public override void DownloadChapter(Publication publication, Chapter chapter, DownloadChapterTask parentTask) + { + while (this.browser is null) + { + logger?.WriteLine(this.GetType().ToString(), "Waiting for headless browser to download..."); + Thread.Sleep(1000); + } + + IPage page = browser.NewPageAsync().Result; + IResponse response = page.GoToAsync(chapter.url).Result; + if (response.Ok) + { + HtmlDocument document = new (); + document.LoadHtml(page.GetContentAsync().Result); + + HtmlNode gallery = document.DocumentNode.Descendants("div").First(div => div.HasClass("ImageGallery")); + HtmlNode[] images = gallery.Descendants("img").Where(img => img.HasClass("img-fluid")).ToArray(); + List urls = new(); + foreach(HtmlNode galleryImage in images) + urls.Add(galleryImage.GetAttributeValue("src", "")); + + string comicInfoPath = Path.GetTempFileName(); + File.WriteAllText(comicInfoPath, GetComicInfoXmlString(publication, chapter, logger)); + + DownloadChapterImages(urls.ToArray(), GetArchiveFilePath(publication, chapter), (byte)1, parentTask, comicInfoPath); + } + } +} \ No newline at end of file diff --git a/Tranga/Publication.cs b/Tranga/Publication.cs index 5393afe..82a1247 100644 --- a/Tranga/Publication.cs +++ b/Tranga/Publication.cs @@ -109,7 +109,7 @@ public readonly struct Publication this.year = year; if(status.ToLower() == "ongoing" || status.ToLower() == "hiatus") this.status = "Continuing"; - else if (status.ToLower() == "completed" || status.ToLower() == "cancelled") + else if (status.ToLower() == "completed" || status.ToLower() == "cancelled" || status.ToLower() == "discontinued") this.status = "Ended"; else this.status = status; diff --git a/Tranga/TaskManager.cs b/Tranga/TaskManager.cs index d890bad..94f106b 100644 --- a/Tranga/TaskManager.cs +++ b/Tranga/TaskManager.cs @@ -34,7 +34,8 @@ public class TaskManager this._connectors = new Connector[] { new MangaDex(downloadFolderPath, imageCachePath, logger), - new Manganato(downloadFolderPath, imageCachePath, logger) + new Manganato(downloadFolderPath, imageCachePath, logger), + new Mangasee(downloadFolderPath, imageCachePath, logger) }; Thread taskChecker = new(TaskCheckerThread); @@ -64,7 +65,8 @@ public class TaskManager this._connectors = new Connector[] { new MangaDex(settings.downloadLocation, settings.coverImageCache, logger), - new Manganato(settings.downloadLocation, settings.coverImageCache, logger) + new Manganato(settings.downloadLocation, settings.coverImageCache, logger), + new Mangasee(settings.downloadLocation, settings.coverImageCache, logger) }; this.settings = settings; diff --git a/Tranga/Tranga.csproj b/Tranga/Tranga.csproj index d735d48..a2edb49 100644 --- a/Tranga/Tranga.csproj +++ b/Tranga/Tranga.csproj @@ -9,6 +9,7 @@ +