Compare commits

..

No commits in common. "9f30e52713e3931057f19d4234a547466b45574b" and "e274c864f9f523f451f615ea89fb82986535a4b9" have entirely different histories.

13 changed files with 135 additions and 180 deletions

View File

@ -10,7 +10,7 @@ namespace Tranga;
public readonly struct Chapter
{
// ReSharper disable once MemberCanBePrivate.Global
public Manga parentManga { get; }
public Publication parentPublication { get; }
public string? name { get; }
public string? volumeNumber { get; }
public string chapterNumber { get; }
@ -20,9 +20,9 @@ public readonly struct Chapter
private static readonly Regex LegalCharacters = new (@"([A-z]*[0-9]* *\.*-*,*\]*\[*'*\'*\)*\(*~*!*)*");
private static readonly Regex IllegalStrings = new(@"Vol(ume)?.?", RegexOptions.IgnoreCase);
public Chapter(Manga parentManga, string? name, string? volumeNumber, string chapterNumber, string url)
public Chapter(Publication parentPublication, string? name, string? volumeNumber, string chapterNumber, string url)
{
this.parentManga = parentManga;
this.parentPublication = parentPublication;
this.name = name;
this.volumeNumber = volumeNumber;
this.chapterNumber = chapterNumber;
@ -38,7 +38,7 @@ public readonly struct Chapter
public override string ToString()
{
return $"Chapter {parentManga.sortName} {parentManga.internalId} {chapterNumber} {name}";
return $"Chapter {parentPublication.sortName} {parentPublication.internalId} {chapterNumber} {name}";
}
/// <summary>
@ -48,9 +48,9 @@ public readonly struct Chapter
internal bool CheckChapterIsDownloaded(string downloadLocation)
{
string newFilePath = GetArchiveFilePath(downloadLocation);
if (!Directory.Exists(Path.Join(downloadLocation, parentManga.folderName)))
if (!Directory.Exists(Path.Join(downloadLocation, parentPublication.folderName)))
return false;
FileInfo[] archives = new DirectoryInfo(Path.Join(downloadLocation, parentManga.folderName)).GetFiles();
FileInfo[] archives = new DirectoryInfo(Path.Join(downloadLocation, parentPublication.folderName)).GetFiles();
Regex chapterInfoRex = new(@"Ch\.[0-9.]+");
Regex chapterRex = new(@"[0-9]+(\.[0-9]+)?");
@ -71,7 +71,7 @@ public readonly struct Chapter
/// <returns>Filepath</returns>
internal string GetArchiveFilePath(string downloadLocation)
{
return Path.Join(downloadLocation, parentManga.folderName, $"{parentManga.folderName} - {this.fileName}.cbz");
return Path.Join(downloadLocation, parentPublication.folderName, $"{parentPublication.folderName} - {this.fileName}.cbz");
}
/// <summary>
@ -82,10 +82,10 @@ public readonly struct Chapter
internal string GetComicInfoXmlString()
{
XElement comicInfo = new XElement("ComicInfo",
new XElement("Tags", string.Join(',', parentManga.tags)),
new XElement("LanguageISO", parentManga.originalLanguage),
new XElement("Tags", string.Join(',', parentPublication.tags)),
new XElement("LanguageISO", parentPublication.originalLanguage),
new XElement("Title", this.name),
new XElement("Writer", string.Join(',', parentManga.authors)),
new XElement("Writer", string.Join(',', parentPublication.authors)),
new XElement("Volume", this.volumeNumber),
new XElement("Number", this.chapterNumber)
);

View File

@ -11,7 +11,7 @@ public abstract class GlobalBase
protected TrangaSettings settings { get; init; }
protected HashSet<NotificationConnector> notificationConnectors { get; init; }
protected HashSet<LibraryConnector> libraryConnectors { get; init; }
protected List<Manga> cachedPublications { get; init; }
protected List<Publication> cachedPublications { get; init; }
protected GlobalBase(GlobalBase clone)
{

View File

@ -14,7 +14,7 @@ public class DownloadChapter : Job
protected override string GetId()
{
return Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Concat(this.GetType().ToString(), chapter.parentManga.internalId, chapter.chapterNumber)));
return Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Concat(this.GetType().ToString(), chapter.parentPublication.internalId, chapter.chapterNumber)));
}
public override string ToString()
@ -28,7 +28,7 @@ public class DownloadChapter : Job
{
mangaConnector.DownloadChapter(chapter, this.progressToken);
UpdateLibraries();
SendNotifications("Chapter downloaded", $"{chapter.parentManga.sortName} - {chapter.chapterNumber}");
SendNotifications("Chapter downloaded", $"{chapter.parentPublication.sortName} - {chapter.chapterNumber}");
});
downloadTask.Start();
return Array.Empty<Job>();

View File

@ -5,26 +5,26 @@ namespace Tranga.Jobs;
public class DownloadNewChapters : Job
{
public Manga manga { get; init; }
public Publication publication { get; init; }
public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Manga manga, bool recurring = false, TimeSpan? recurrence = null) : base (clone, connector, recurring, recurrence)
public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Publication publication, bool recurring = false, TimeSpan? recurrence = null) : base (clone, connector, recurring, recurrence)
{
this.manga = manga;
this.publication = publication;
}
protected override string GetId()
{
return Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Concat(this.GetType().ToString(), manga.internalId)));
return Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Concat(this.GetType().ToString(), publication.internalId)));
}
public override string ToString()
{
return $"DownloadChapter {id} {manga}";
return $"DownloadChapter {id} {publication}";
}
protected override IEnumerable<Job> ExecuteReturnSubTasksInternal()
{
Chapter[] chapters = mangaConnector.GetNewChapters(manga);
Chapter[] chapters = mangaConnector.GetNewChapters(publication);
this.progressToken.increments = chapters.Length;
List<Job> subJobs = new();
foreach (Chapter chapter in chapters)

View File

@ -44,7 +44,7 @@ public class JobBoss : GlobalBase
{
if (jjob is not DownloadChapter job)
return false;
return job.chapter.parentManga.internalId == internalId &&
return job.chapter.parentPublication.internalId == internalId &&
job.chapter.chapterNumber == chapterNumber;
});
else if (internalId is not null)
@ -52,43 +52,24 @@ public class JobBoss : GlobalBase
{
if (jjob is not DownloadNewChapters job)
return false;
return job.manga.internalId == internalId;
return job.publication.internalId == internalId;
});
return ret;
}
public IEnumerable<Job> GetJobsLike(MangaConnector? mangaConnector = null, Manga? publication = null,
public IEnumerable<Job> GetJobsLike(MangaConnector? mangaConnector = null, Publication? publication = null,
Chapter? chapter = null)
{
return GetJobsLike(mangaConnector?.name, publication?.internalId, chapter?.chapterNumber);
}
public Job? GetJobById(string jobId)
{
if (this.jobs.FirstOrDefault(jjob => jjob.id == jobId) is { } job)
return job;
return null;
}
public bool TryGetJobById(string jobId, out Job? job)
{
if (this.jobs.FirstOrDefault(jjob => jjob.id == jobId) is { } ret)
{
job = ret;
return true;
}
job = null;
return false;
}
private bool QueueContainsJob(Job job)
{
mangaConnectorJobQueue.TryAdd(job.mangaConnector, new Queue<Job>());
return mangaConnectorJobQueue[job.mangaConnector].Contains(job);
}
public void AddJobToQueue(Job job)
private void AddJobToQueue(Job job)
{
Log($"Adding Job to Queue. {job}");
mangaConnectorJobQueue.TryAdd(job.mangaConnector, new Queue<Job>());

View File

@ -29,40 +29,40 @@ public abstract class MangaConnector : GlobalBase
/// </summary>
/// <param name="publicationTitle">Search-Query</param>
/// <returns>Publications matching the query</returns>
public abstract Manga[] GetPublications(string publicationTitle = "");
public abstract Publication[] GetPublications(string publicationTitle = "");
/// <summary>
/// Returns all Chapters of the publication in the provided language.
/// If the language is empty or null, returns all Chapters in all Languages.
/// </summary>
/// <param name="manga">Publication to get Chapters for</param>
/// <param name="publication">Publication to get Chapters for</param>
/// <param name="language">Language of the Chapters</param>
/// <returns>Array of Chapters matching Publication and Language</returns>
public abstract Chapter[] GetChapters(Manga manga, string language="en");
public abstract Chapter[] GetChapters(Publication publication, string language="en");
/// <summary>
/// Updates the available Chapters of a Publication
/// </summary>
/// <param name="manga">Publication to check</param>
/// <param name="publication">Publication to check</param>
/// <param name="language">Language to receive chapters for</param>
/// <returns>List of Chapters that were previously not in collection</returns>
public Chapter[] GetNewChapters(Manga manga, string language = "en")
public Chapter[] GetNewChapters(Publication publication, string language = "en")
{
Log($"Getting new Chapters for {manga}");
Chapter[] newChapters = this.GetChapters(manga, language);
Log($"Getting new Chapters for {publication}");
Chapter[] newChapters = this.GetChapters(publication, language);
NumberFormatInfo decimalPoint = new (){ NumberDecimalSeparator = "." };
Log($"Checking for duplicates {manga}");
Log($"Checking for duplicates {publication}");
List<Chapter> newChaptersList = newChapters.Where(nChapter =>
float.Parse(nChapter.chapterNumber, decimalPoint) > manga.ignoreChaptersBelow &&
float.Parse(nChapter.chapterNumber, decimalPoint) > publication.ignoreChaptersBelow &&
!nChapter.CheckChapterIsDownloaded(settings.downloadLocation)).ToList();
Log($"{newChaptersList.Count} new chapters. {manga}");
Log($"{newChaptersList.Count} new chapters. {publication}");
return newChaptersList.ToArray();
}
public Chapter[] SelectChapters(Manga manga, string searchTerm, string? language = null)
public Chapter[] SelectChapters(Publication publication, string searchTerm, string? language = null)
{
Chapter[] availableChapters = this.GetChapters(manga, language??"en");
Chapter[] availableChapters = this.GetChapters(publication, language??"en");
Regex volumeRegex = new ("((v(ol)*(olume)*){1} *([0-9]+(-[0-9]+)?){1})", RegexOptions.IgnoreCase);
Regex chapterRegex = new ("((c(h)*(hapter)*){1} *([0-9]+(-[0-9]+)?){1})", RegexOptions.IgnoreCase);
Regex singleResultRegex = new("([0-9]+)", RegexOptions.IgnoreCase);
@ -138,20 +138,20 @@ public abstract class MangaConnector : GlobalBase
/// <summary>
/// Copies the already downloaded cover from cache to downloadLocation
/// </summary>
/// <param name="manga">Publication to retrieve Cover for</param>
public void CopyCoverFromCacheToDownloadLocation(Manga manga)
/// <param name="publication">Publication to retrieve Cover for</param>
public void CopyCoverFromCacheToDownloadLocation(Publication publication)
{
Log($"Copy cover {manga}");
Log($"Copy cover {publication}");
//Check if Publication already has a Folder and cover
string publicationFolder = manga.CreatePublicationFolder(settings.downloadLocation);
string publicationFolder = publication.CreatePublicationFolder(settings.downloadLocation);
DirectoryInfo dirInfo = new (publicationFolder);
if (dirInfo.EnumerateFiles().Any(info => info.Name.Contains("cover", StringComparison.InvariantCultureIgnoreCase)))
{
Log($"Cover exists {manga}");
Log($"Cover exists {publication}");
return;
}
string fileInCache = Path.Join(settings.coverImageCache, manga.coverFileNameInCache);
string fileInCache = Path.Join(settings.coverImageCache, publication.coverFileNameInCache);
string newFilePath = Path.Join(publicationFolder, $"cover.{Path.GetFileName(fileInCache).Split('.')[^1]}" );
Log($"Cloning cover {fileInCache} -> {newFilePath}");
File.Copy(fileInCache, newFilePath, true);

View File

@ -31,13 +31,13 @@ public class MangaDex : MangaConnector
});
}
public override Manga[] GetPublications(string publicationTitle = "")
public override Publication[] GetPublications(string publicationTitle = "")
{
Log($"Searching Publications. Term=\"{publicationTitle}\"");
const int limit = 100; //How many values we want returned at once
int offset = 0; //"Page"
int total = int.MaxValue; //How many total results are there, is updated on first request
HashSet<Manga> publications = new();
HashSet<Publication> publications = new();
int loadedPublicationData = 0;
while (offset < total) //As long as we haven't requested all "Pages"
{
@ -102,6 +102,9 @@ public class MangaDex : MangaConnector
authorIds.Add(node!["id"]!.GetValue<string>());
}
string? coverUrl = GetCoverUrl(publicationId, posterId);
string? coverCacheName = null;
if (coverUrl is not null)
coverCacheName = SaveCoverImageToCache(coverUrl, (byte)RequestType.AtHomeServer);
List<string> authors = GetAuthors(authorIds);
@ -125,13 +128,14 @@ public class MangaDex : MangaConnector
string status = attributes["status"]!.GetValue<string>();
Manga pub = new (
Publication pub = new (
title,
authors,
description,
altTitlesDict,
tags.ToArray(),
coverUrl,
coverCacheName,
linksDict,
year,
originalLanguage,
@ -147,9 +151,9 @@ public class MangaDex : MangaConnector
return publications.ToArray();
}
public override Chapter[] GetChapters(Manga manga, string language="en")
public override Chapter[] GetChapters(Publication publication, string language="en")
{
Log($"Getting chapters {manga}");
Log($"Getting chapters {publication}");
const int limit = 100; //How many values we want returned at once
int offset = 0; //"Page"
int total = int.MaxValue; //How many total results are there, is updated on first request
@ -160,7 +164,7 @@ public class MangaDex : MangaConnector
//Request next "Page"
DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest(
$"https://api.mangadex.org/manga/{manga.publicationId}/feed?limit={limit}&offset={offset}&translatedLanguage%5B%5D={language}", (byte)RequestType.Feed);
$"https://api.mangadex.org/manga/{publication.publicationId}/feed?limit={limit}&offset={offset}&translatedLanguage%5B%5D={language}", (byte)RequestType.Feed);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
break;
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
@ -191,13 +195,13 @@ public class MangaDex : MangaConnector
: "null";
if(chapterNum is not "null")
chapters.Add(new Chapter(manga, title, volume, chapterNum, chapterId));
chapters.Add(new Chapter(publication, title, volume, chapterNum, chapterId));
}
}
//Return Chapters ordered by Chapter-Number
NumberFormatInfo chapterNumberFormatInfo = new() { NumberDecimalSeparator = "." };
Log($"Got {chapters.Count} chapters. {manga}");
Log($"Got {chapters.Count} chapters. {publication}");
return chapters.OrderBy(chapter => Convert.ToSingle(chapter.chapterNumber, chapterNumberFormatInfo)).ToArray();
}
@ -205,8 +209,7 @@ public class MangaDex : MangaConnector
{
if (progressToken?.cancellationRequested ?? false)
return HttpStatusCode.RequestTimeout;
Manga chapterParentManga = chapter.parentManga;
Log($"Retrieving chapter-info {chapter} {chapterParentManga}");
Log($"Retrieving chapter-info {chapter} {chapter.parentPublication}");
//Request URLs for Chapter-Images
DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest($"https://api.mangadex.org/at-home/server/{chapter.url}?forcePort443=false'", (byte)RequestType.AtHomeServer);
@ -227,8 +230,6 @@ public class MangaDex : MangaConnector
string comicInfoPath = Path.GetTempFileName();
File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString());
if (chapterParentManga.coverUrl is not null)
chapterParentManga.coverFileNameInCache = SaveCoverImageToCache(chapterParentManga.coverUrl, (byte)RequestType.AtHomeServer);
//Download Chapter-Images
return DownloadChapterImages(imageUrls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), (byte)RequestType.AtHomeServer, comicInfoPath, progressToken:progressToken);
}

View File

@ -19,7 +19,7 @@ public class MangaKatana : MangaConnector
});
}
public override Manga[] GetPublications(string publicationTitle = "")
public override Publication[] GetPublications(string publicationTitle = "")
{
Log($"Searching Publications. Term=\"{publicationTitle}\"");
string sanitizedTitle = string.Join('_', Regex.Matches(publicationTitle, "[A-z]*").Where(m => m.Value.Length > 0)).ToLower();
@ -27,7 +27,7 @@ public class MangaKatana : MangaConnector
DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest(requestUrl, 1);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return Array.Empty<Manga>();
return Array.Empty<Publication>();
// ReSharper disable once MergeIntoPattern
// If a single result is found, the user will be redirected to the results directly instead of a result page
@ -38,13 +38,13 @@ public class MangaKatana : MangaConnector
return new [] { ParseSinglePublicationFromHtml(requestResult.result, requestResult.redirectedToUrl.Split('/')[^1]) };
}
Manga[] publications = ParsePublicationsFromHtml(requestResult.result);
Publication[] publications = ParsePublicationsFromHtml(requestResult.result);
cachedPublications.AddRange(publications);
Log($"Retrieved {publications.Length} publications. Term=\"{publicationTitle}\"");
return publications;
}
private Manga[] ParsePublicationsFromHtml(Stream html)
private Publication[] ParsePublicationsFromHtml(Stream html)
{
StreamReader reader = new(html);
string htmlString = reader.ReadToEnd();
@ -52,7 +52,7 @@ public class MangaKatana : MangaConnector
document.LoadHtml(htmlString);
IEnumerable<HtmlNode> searchResults = document.DocumentNode.SelectNodes("//*[@id='book_list']/div");
if (searchResults is null || !searchResults.Any())
return Array.Empty<Manga>();
return Array.Empty<Publication>();
List<string> urls = new();
foreach (HtmlNode mangaResult in searchResults)
{
@ -60,13 +60,13 @@ public class MangaKatana : MangaConnector
.First(a => a.Name == "href").Value);
}
HashSet<Manga> ret = new();
HashSet<Publication> ret = new();
foreach (string url in urls)
{
DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest(url, 1);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return Array.Empty<Manga>();
return Array.Empty<Publication>();
ret.Add(ParseSinglePublicationFromHtml(requestResult.result, url.Split('/')[^1]));
}
@ -74,7 +74,7 @@ public class MangaKatana : MangaConnector
return ret.ToArray();
}
private Manga ParseSinglePublicationFromHtml(Stream html, string publicationId)
private Publication ParseSinglePublicationFromHtml(Stream html, string publicationId)
{
StreamReader reader = new(html);
string htmlString = reader.ReadToEnd();
@ -119,6 +119,8 @@ public class MangaKatana : MangaConnector
string posterUrl = document.DocumentNode.SelectSingleNode("//*[@id='single_book']/div[1]/div").Descendants("img").First()
.GetAttributes().First(a => a.Name == "src").Value;
string coverFileNameInCache = SaveCoverImageToCache(posterUrl, 1);
string description = document.DocumentNode.SelectSingleNode("//*[@id='single_book']/div[3]/p").InnerText;
while (description.StartsWith('\n'))
description = description.Substring(1);
@ -132,14 +134,14 @@ public class MangaKatana : MangaConnector
year = Convert.ToInt32(yearString);
}
return new Manga(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, links,
return new Publication(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links,
year, originalLanguage, status, publicationId);
}
public override Chapter[] GetChapters(Manga manga, string language="en")
public override Chapter[] GetChapters(Publication publication, string language="en")
{
Log($"Getting chapters {manga}");
string requestUrl = $"https://mangakatana.com/manga/{manga.publicationId}";
Log($"Getting chapters {publication}");
string requestUrl = $"https://mangakatana.com/manga/{publication.publicationId}";
// Leaving this in for verification if the page exists
DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest(requestUrl, 1);
@ -151,12 +153,12 @@ public class MangaKatana : MangaConnector
{
NumberDecimalSeparator = "."
};
List<Chapter> chapters = ParseChaptersFromHtml(manga, requestUrl);
Log($"Got {chapters.Count} chapters. {manga}");
List<Chapter> chapters = ParseChaptersFromHtml(publication, requestUrl);
Log($"Got {chapters.Count} chapters. {publication}");
return chapters.OrderBy(chapter => Convert.ToSingle(chapter.chapterNumber, chapterNumberFormatInfo)).ToArray();
}
private List<Chapter> ParseChaptersFromHtml(Manga manga, string mangaUrl)
private List<Chapter> ParseChaptersFromHtml(Publication publication, string mangaUrl)
{
// Using HtmlWeb will include the chapters since they are loaded with js
HtmlWeb web = new();
@ -175,7 +177,7 @@ public class MangaKatana : MangaConnector
string chapterName = string.Concat(fullString.Split(':')[1..]);
string url = chapterInfo.Descendants("a").First()
.GetAttributeValue("href", "");
ret.Add(new Chapter(manga, chapterName, volumeNumber, chapterNumber, url));
ret.Add(new Chapter(publication, chapterName, volumeNumber, chapterNumber, url));
}
return ret;
@ -185,8 +187,7 @@ public class MangaKatana : MangaConnector
{
if (progressToken?.cancellationRequested ?? false)
return HttpStatusCode.RequestTimeout;
Manga chapterParentManga = chapter.parentManga;
Log($"Retrieving chapter-info {chapter} {chapterParentManga}");
Log($"Retrieving chapter-info {chapter} {chapter.parentPublication}");
string requestUrl = chapter.url;
// Leaving this in to check if the page exists
DownloadClient.RequestResult requestResult =
@ -199,9 +200,6 @@ public class MangaKatana : MangaConnector
string comicInfoPath = Path.GetTempFileName();
File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString());
if (chapterParentManga.coverUrl is not null)
chapterParentManga.coverFileNameInCache = SaveCoverImageToCache(chapterParentManga.coverUrl, 1);
return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, "https://mangakatana.com/", progressToken:progressToken);
}

View File

@ -19,7 +19,7 @@ public class Manganato : MangaConnector
});
}
public override Manga[] GetPublications(string publicationTitle = "")
public override Publication[] GetPublications(string publicationTitle = "")
{
Log($"Searching Publications. Term=\"{publicationTitle}\"");
string sanitizedTitle = string.Join('_', Regex.Matches(publicationTitle, "[A-z]*")).ToLower();
@ -27,15 +27,15 @@ public class Manganato : MangaConnector
DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest(requestUrl, 1);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return Array.Empty<Manga>();
return Array.Empty<Publication>();
Manga[] publications = ParsePublicationsFromHtml(requestResult.result);
Publication[] publications = ParsePublicationsFromHtml(requestResult.result);
cachedPublications.AddRange(publications);
Log($"Retrieved {publications.Length} publications. Term=\"{publicationTitle}\"");
return publications;
}
private Manga[] ParsePublicationsFromHtml(Stream html)
private Publication[] ParsePublicationsFromHtml(Stream html)
{
StreamReader reader = new (html);
string htmlString = reader.ReadToEnd();
@ -49,13 +49,13 @@ public class Manganato : MangaConnector
.First(a => a.Name == "href").Value);
}
HashSet<Manga> ret = new();
HashSet<Publication> ret = new();
foreach (string url in urls)
{
DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest(url, 1);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return Array.Empty<Manga>();
return Array.Empty<Publication>();
ret.Add(ParseSinglePublicationFromHtml(requestResult.result, url.Split('/')[^1]));
}
@ -63,7 +63,7 @@ public class Manganato : MangaConnector
return ret.ToArray();
}
private Manga ParseSinglePublicationFromHtml(Stream html, string publicationId)
private Publication ParseSinglePublicationFromHtml(Stream html, string publicationId)
{
StreamReader reader = new (html);
string htmlString = reader.ReadToEnd();
@ -111,6 +111,8 @@ public class Manganato : MangaConnector
string posterUrl = document.DocumentNode.Descendants("span").First(s => s.HasClass("info-image")).Descendants("img").First()
.GetAttributes().First(a => a.Name == "src").Value;
string coverFileNameInCache = SaveCoverImageToCache(posterUrl, 1);
string description = document.DocumentNode.Descendants("div").First(d => d.HasClass("panel-story-info-description"))
.InnerText.Replace("Description :", "");
while (description.StartsWith('\n'))
@ -120,14 +122,14 @@ public class Manganato : MangaConnector
.First(s => s.HasClass("chapter-time")).InnerText;
int year = Convert.ToInt32(yearString.Split(',')[^1]) + 2000;
return new Manga(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, links,
return new Publication(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links,
year, originalLanguage, status, publicationId);
}
public override Chapter[] GetChapters(Manga manga, string language="en")
public override Chapter[] GetChapters(Publication publication, string language="en")
{
Log($"Getting chapters {manga}");
string requestUrl = $"https://chapmanganato.com/{manga.publicationId}";
Log($"Getting chapters {publication}");
string requestUrl = $"https://chapmanganato.com/{publication.publicationId}";
DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest(requestUrl, 1);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
@ -138,12 +140,12 @@ public class Manganato : MangaConnector
{
NumberDecimalSeparator = "."
};
List<Chapter> chapters = ParseChaptersFromHtml(manga, requestResult.result);
Log($"Got {chapters.Count} chapters. {manga}");
List<Chapter> chapters = ParseChaptersFromHtml(publication, requestResult.result);
Log($"Got {chapters.Count} chapters. {publication}");
return chapters.OrderBy(chapter => Convert.ToSingle(chapter.chapterNumber, chapterNumberFormatInfo)).ToArray();
}
private List<Chapter> ParseChaptersFromHtml(Manga manga, Stream html)
private List<Chapter> ParseChaptersFromHtml(Publication publication, Stream html)
{
StreamReader reader = new (html);
string htmlString = reader.ReadToEnd();
@ -162,7 +164,7 @@ public class Manganato : MangaConnector
string chapterName = string.Concat(fullString.Split(':')[1..]);
string url = chapterInfo.Descendants("a").First(d => d.HasClass("chapter-name"))
.GetAttributeValue("href", "");
ret.Add(new Chapter(manga, chapterName, volumeNumber, chapterNumber, url));
ret.Add(new Chapter(publication, chapterName, volumeNumber, chapterNumber, url));
}
ret.Reverse();
return ret;
@ -172,8 +174,7 @@ public class Manganato : MangaConnector
{
if (progressToken?.cancellationRequested ?? false)
return HttpStatusCode.RequestTimeout;
Manga chapterParentManga = chapter.parentManga;
Log($"Retrieving chapter-info {chapter} {chapterParentManga}");
Log($"Retrieving chapter-info {chapter} {chapter.parentPublication}");
string requestUrl = chapter.url;
DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest(requestUrl, 1);
@ -185,9 +186,6 @@ public class Manganato : MangaConnector
string comicInfoPath = Path.GetTempFileName();
File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString());
if (chapterParentManga.coverUrl is not null)
chapterParentManga.coverFileNameInCache = SaveCoverImageToCache(chapterParentManga.coverUrl, 1);
return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, "https://chapmanganato.com/", progressToken:progressToken);
}

View File

@ -69,22 +69,22 @@ public class Mangasee : MangaConnector
});
}
public override Manga[] GetPublications(string publicationTitle = "")
public override Publication[] GetPublications(string publicationTitle = "")
{
Log($"Searching Publications. Term=\"{publicationTitle}\"");
string requestUrl = $"https://mangasee123.com/_search.php";
DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest(requestUrl, 1);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return Array.Empty<Manga>();
return Array.Empty<Publication>();
Manga[] publications = ParsePublicationsFromHtml(requestResult.result, publicationTitle);
Publication[] publications = ParsePublicationsFromHtml(requestResult.result, publicationTitle);
cachedPublications.AddRange(publications);
Log($"Retrieved {publications.Length} publications. Term=\"{publicationTitle}\"");
return publications;
}
private Manga[] ParsePublicationsFromHtml(Stream html, string publicationTitle)
private Publication[] ParsePublicationsFromHtml(Stream html, string publicationTitle)
{
string jsonString = new StreamReader(html).ReadToEnd();
List<SearchResultItem> result = JsonConvert.DeserializeObject<List<SearchResultItem>>(jsonString)!;
@ -101,7 +101,7 @@ public class Mangasee : MangaConnector
Log($"Retrieved {queryFiltered.Count} publications.");
HashSet<Manga> ret = new();
HashSet<Publication> ret = new();
List<SearchResultItem> orderedFiltered =
queryFiltered.OrderBy(item => item.Value).ToDictionary(item => item.Key, item => item.Value).Keys.ToList();
@ -120,7 +120,7 @@ public class Mangasee : MangaConnector
}
private Manga ParseSinglePublicationFromHtml(Stream html, string sortName, string publicationId, string[] a)
private Publication ParseSinglePublicationFromHtml(Stream html, string sortName, string publicationId, string[] a)
{
StreamReader reader = new (html);
HtmlDocument document = new ();
@ -133,6 +133,7 @@ public class Mangasee : MangaConnector
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"))
@ -170,7 +171,7 @@ public class Mangasee : MangaConnector
foreach(string at in a)
altTitles.Add((i++).ToString(), at);
return new Manga(sortName, authors, description, altTitles, tags.ToArray(), posterUrl, links,
return new Publication(sortName, authors, description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links,
year, originalLanguage, status, publicationId);
}
@ -214,10 +215,10 @@ public class Mangasee : MangaConnector
}
}
public override Chapter[] GetChapters(Manga manga, string language="en")
public override Chapter[] GetChapters(Publication publication, string language="en")
{
Log($"Getting chapters {manga}");
XDocument doc = XDocument.Load($"https://mangasee123.com/rss/{manga.publicationId}.xml");
Log($"Getting chapters {publication}");
XDocument doc = XDocument.Load($"https://mangasee123.com/rss/{publication.publicationId}.xml");
XElement[] chapterItems = doc.Descendants("item").ToArray();
List<Chapter> chapters = new();
foreach (XElement chapter in chapterItems)
@ -228,7 +229,7 @@ public class Mangasee : MangaConnector
string url = chapter.Descendants("link").First().Value;
url = url.Replace(Regex.Matches(url,"(-page-[0-9])")[0].ToString(),"");
chapters.Add(new Chapter(manga, "", volumeNumber, chapterNumber, url));
chapters.Add(new Chapter(publication, "", volumeNumber, chapterNumber, url));
}
//Return Chapters ordered by Chapter-Number
@ -236,7 +237,7 @@ public class Mangasee : MangaConnector
{
NumberDecimalSeparator = "."
};
Log($"Got {chapters.Count} chapters. {manga}");
Log($"Got {chapters.Count} chapters. {publication}");
return chapters.OrderBy(chapter => Convert.ToSingle(chapter.chapterNumber, chapterNumberFormatInfo)).ToArray();
}
@ -244,7 +245,6 @@ public class Mangasee : MangaConnector
{
if (progressToken?.cancellationRequested ?? false)
return HttpStatusCode.RequestTimeout;
Manga chapterParentManga = chapter.parentManga;
while (this._browser is null && !(progressToken?.cancellationRequested??false))
{
Log("Waiting for headless browser to download...");
@ -253,7 +253,7 @@ public class Mangasee : MangaConnector
if (progressToken?.cancellationRequested??false)
return HttpStatusCode.RequestTimeout;
Log($"Retrieving chapter-info {chapter} {chapterParentManga}");
Log($"Retrieving chapter-info {chapter} {chapter.parentPublication}");
IPage page = _browser!.NewPageAsync().Result;
IResponse response = page.GoToAsync(chapter.url).Result;
if (response.Ok)
@ -270,9 +270,6 @@ public class Mangasee : MangaConnector
string comicInfoPath = Path.GetTempFileName();
File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString());
if (chapterParentManga.coverUrl is not null)
chapterParentManga.coverFileNameInCache = SaveCoverImageToCache(chapterParentManga.coverUrl, 1);
return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, progressToken:progressToken);
}
return response.Status;

View File

@ -9,7 +9,7 @@ namespace Tranga;
/// <summary>
/// Contains information on a Publication (Manga)
/// </summary>
public struct Manga
public struct Publication
{
public string sortName { get; }
public List<string> authors { get; }
@ -19,8 +19,8 @@ public struct Manga
public string? description { get; }
public string[] tags { get; }
// ReSharper disable once UnusedAutoPropertyAccessor.Global
public string? coverUrl { get; }
public string? coverFileNameInCache { get; set; }
public string? posterUrl { get; }
public string? coverFileNameInCache { get; }
// ReSharper disable once UnusedAutoPropertyAccessor.Global
public Dictionary<string,string> links { get; }
// ReSharper disable once MemberCanBePrivate.Global
@ -36,15 +36,15 @@ public struct Manga
private static readonly Regex LegalCharacters = new (@"[A-Z]*[a-z]*[0-9]* *\.*-*,*'*\'*\)*\(*~*!*");
[JsonConstructor]
public Manga(string sortName, List<string> authors, string? description, Dictionary<string,string> altTitles, string[] tags, string? coverUrl, Dictionary<string,string>? links, int? year, string? originalLanguage, string status, string publicationId, string? folderName = null, float? ignoreChaptersBelow = 0)
public Publication(string sortName, List<string> authors, string? description, Dictionary<string,string> altTitles, string[] tags, string? posterUrl, string? coverFileNameInCache, Dictionary<string,string>? links, int? year, string? originalLanguage, string status, string publicationId, string? folderName = null, float? ignoreChaptersBelow = 0)
{
this.sortName = sortName;
this.authors = authors;
this.description = description;
this.altTitles = altTitles;
this.tags = tags;
this.coverFileNameInCache = null;
this.coverUrl = coverUrl;
this.coverFileNameInCache = coverFileNameInCache;
this.posterUrl = posterUrl;
this.links = links ?? new Dictionary<string, string>();
this.year = year;
this.originalLanguage = originalLanguage;

View File

@ -113,7 +113,7 @@ public class Server : GlobalBase
case "Connectors":
SendResponse(HttpStatusCode.OK, response, _parent.GetConnectors().Select(con => con.name).ToArray());
break;
case "Manga/FromConnector":
case "Publications/FromConnector":
if (!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("title", out string? title) ||
_parent.GetConnector(connectorName) is null)
@ -124,7 +124,7 @@ public class Server : GlobalBase
connector = _parent.GetConnector(connectorName)!;
SendResponse(HttpStatusCode.OK, response, connector.GetPublications(title));
break;
case "Manga/Chapters":
case "Publications/Chapters":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out string? internalId) ||
_parent.GetConnector(connectorName) is null ||
@ -134,10 +134,10 @@ public class Server : GlobalBase
break;
}
connector = _parent.GetConnector(connectorName)!;
Manga manga = (Manga)_parent.GetPublicationById(internalId)!;
SendResponse(HttpStatusCode.OK, response, connector.GetChapters(manga));
Publication publication = (Publication)_parent.GetPublicationById(internalId)!;
SendResponse(HttpStatusCode.OK, response, connector.GetChapters(publication));
break;
case "Jobs":
case "Tasks":
if (!requestVariables.TryGetValue("jobId", out jobId))
{
if(!_parent._jobBoss.jobs.Any(jjob => jjob.id == jobId))
@ -148,7 +148,7 @@ public class Server : GlobalBase
}
SendResponse(HttpStatusCode.OK, response, _parent._jobBoss.jobs);
break;
case "Jobs/Progress":
case "Tasks/Progress":
if (!requestVariables.TryGetValue("jobId", out jobId))
{
if(!_parent._jobBoss.jobs.Any(jjob => jjob.id == jobId))
@ -159,10 +159,10 @@ public class Server : GlobalBase
}
SendResponse(HttpStatusCode.OK, response, _parent._jobBoss.jobs.Select(jjob => jjob.progressToken));
break;
case "Jobs/Running":
case "Tasks/Running":
SendResponse(HttpStatusCode.OK, response, _parent._jobBoss.jobs.Where(jjob => jjob.progressToken.state is ProgressToken.State.Running));
break;
case "Jobs/Waiting":
case "Tasks/Waiting":
SendResponse(HttpStatusCode.OK, response, _parent._jobBoss.jobs.Where(jjob => jjob.progressToken.state is ProgressToken.State.Standby));
break;
case "Settings":
@ -191,11 +191,11 @@ public class Server : GlobalBase
Dictionary<string, string> requestVariables = GetRequestVariables(request.Url!.Query);
string? connectorName, internalId;
MangaConnector connector;
Manga manga;
Publication publication;
string path = Regex.Match(request.Url!.LocalPath, @"[A-z0-9]+(\/[A-z0-9]+)*").Value;
switch (path)
{
case "Jobs/MonitorManga":
case "Tasks/MonitorManga":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
!requestVariables.TryGetValue("interval", out string? intervalStr) ||
@ -207,11 +207,11 @@ public class Server : GlobalBase
break;
}
connector = _parent.GetConnector(connectorName)!;
manga = (Manga)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.AddJob(new DownloadNewChapters(this, connector, manga, true, interval));
publication = (Publication)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.AddJob(new DownloadNewChapters(this, connector, publication, true, interval));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Jobs/DownloadNewChapters":
case "Tasks/DownloadNewChapters":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
_parent.GetConnector(connectorName) is null ||
@ -221,18 +221,8 @@ public class Server : GlobalBase
break;
}
connector = _parent.GetConnector(connectorName)!;
manga = (Manga)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.AddJob(new DownloadNewChapters(this, connector, manga, false));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Jobs/StartNow":
if (!requestVariables.TryGetValue("jobId", out string? jobId) ||
!_parent._jobBoss.TryGetJobById(jobId, out Job? job))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
_parent._jobBoss.AddJobToQueue(job!);
publication = (Publication)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.AddJob(new DownloadNewChapters(this, connector, publication, false));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Settings/UpdateDownloadLocation":
@ -335,21 +325,11 @@ public class Server : GlobalBase
Dictionary<string, string> requestVariables = GetRequestVariables(request.Url!.Query);
string? connectorName, internalId;
MangaConnector connector;
Manga manga;
Publication publication;
string path = Regex.Match(request.Url!.LocalPath, @"[A-z0-9]+(\/[A-z0-9]+)*").Value;
switch (path)
{
case "Jobs":
if (!requestVariables.TryGetValue("jobID", out string? jobId) ||
!_parent._jobBoss.TryGetJobById(jobId, out Job? job))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
_parent._jobBoss.RemoveJob(job!);
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Jobs/DownloadChapter":
case "Tasks/DownloadChapter":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
!requestVariables.TryGetValue("chapterNumber", out string? chapterNumber) ||
@ -362,7 +342,7 @@ public class Server : GlobalBase
_parent._jobBoss.RemoveJobs(_parent._jobBoss.GetJobsLike(connectorName, internalId, chapterNumber));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Jobs/MonitorManga":
case "Tasks/MonitorManga":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
_parent.GetConnector(connectorName) is null ||
@ -372,11 +352,11 @@ public class Server : GlobalBase
break;
}
connector = _parent.GetConnector(connectorName)!;
manga = (Manga)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.RemoveJobs(_parent._jobBoss.GetJobsLike(connector, manga));
publication = (Publication)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.RemoveJobs(_parent._jobBoss.GetJobsLike(connector, publication));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Jobs/DownloadNewChapters":
case "Tasks/DownloadNewChapters":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
_parent.GetConnector(connectorName) is null ||
@ -386,8 +366,8 @@ public class Server : GlobalBase
break;
}
connector = _parent.GetConnector(connectorName)!;
manga = (Manga)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.RemoveJobs(_parent._jobBoss.GetJobsLike(connector, manga));
publication = (Publication)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.RemoveJobs(_parent._jobBoss.GetJobsLike(connector, publication));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "NotificationConnectors":

View File

@ -39,7 +39,7 @@ public partial class Tranga : GlobalBase
return connectors;
}
public Manga? GetPublicationById(string internalId)
public Publication? GetPublicationById(string internalId)
{
if (cachedPublications.Exists(publication => publication.internalId == internalId))
return cachedPublications.First(publication => publication.internalId == internalId);