Compare commits

..

25 Commits

Author SHA1 Message Date
442d949371 Fix #80 UpdateMetaData failing 2023-11-12 13:03:33 +01:00
263d0e6036 Fix #82 Tranga crashes when cover is missing from imageCache.
Retrying download of cover and copy
2023-11-12 12:39:32 +01:00
5cdc7d7207 Fix wrong jobtype 2023-11-05 16:14:23 +01:00
1bcbd1517f Addresses #81 2023-11-05 16:14:12 +01:00
b72da45ae9 Add GetMangaFromId for MangaWorld 2023-11-02 15:58:16 +01:00
01041e43ac Fix publicationId for MangaWorld 2023-11-02 15:58:04 +01:00
4c1a659f16 Add API: POST Jobs/UpdateMetadata 2023-11-02 15:48:46 +01:00
2e02f0b237 Exception message. 2023-11-02 15:48:31 +01:00
77f93d87f9 UpdateMetadata now finishes correctly. 2023-11-02 15:48:17 +01:00
45c0f19a9d Added override Manga.Equals 2023-11-02 15:48:03 +01:00
7c09deb143 Remove Manga.WebsiteUrl 2023-11-02 15:47:43 +01:00
449d406eab Add MangaConnector.GetMangaFromId 2023-11-02 15:47:16 +01:00
083ce238d8 Add UpdateMetadata Job to DownloadNewChapters 2023-11-02 15:20:34 +01:00
5f9ffb8aad Improved UpdateMetadata 2023-11-02 15:20:20 +01:00
92bc3d5aa8 Catch HttpRequestException in LibraryConnector 2023-11-02 15:19:56 +01:00
49ab8928b1 Add parameter JobBoss to Job.ExecuteTask (and Internal) 2023-11-02 15:19:36 +01:00
391efcb9bc Add Field jobType to Job 2023-11-02 15:18:41 +01:00
963ad375e8 Add Job UpdateMetadata --> untested! 2023-11-01 14:17:11 +01:00
0a5ded2036 Add field WebsiteUrl to Manga 2023-11-01 14:15:55 +01:00
4843c7f05c Overwrite SeriesInfo.json parameter in SaveSeriesInfoJson. 2023-11-01 14:04:35 +01:00
6adbda2359 #77 Added field releaseStatus to Manga 2023-11-01 13:59:21 +01:00
425cf7e0d6 Re-add forgotten seriesInfo.json to new downloads 2023-11-01 13:36:58 +01:00
8f5dd5aab5 #78 Manganato chapternumber parsing from url 2023-11-01 13:22:33 +01:00
733ae285f1 #76 debug 2023-10-31 16:46:41 +01:00
2e1c8ce34f #75 Reimplemented own search.
At the moment returns too many results, levenshtein distance still too inefficient.
2023-10-31 15:47:39 +01:00
17 changed files with 402 additions and 85 deletions

View File

@ -7,12 +7,12 @@ public class DownloadChapter : Job
{ {
public Chapter chapter { get; init; } public Chapter chapter { get; init; }
public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, DateTime lastExecution, string? parentJobId = null) : base(clone, connector, lastExecution, parentJobId: parentJobId) public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, DateTime lastExecution, string? parentJobId = null) : base(clone, JobType.DownloadChapterJob, connector, lastExecution, parentJobId: parentJobId)
{ {
this.chapter = chapter; this.chapter = chapter;
} }
public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, string? parentJobId = null) : base(clone, connector, parentJobId: parentJobId) public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, string? parentJobId = null) : base(clone, JobType.DownloadChapterJob, connector, parentJobId: parentJobId)
{ {
this.chapter = chapter; this.chapter = chapter;
} }
@ -27,7 +27,7 @@ public class DownloadChapter : Job
return $"{id} Chapter: {chapter}"; return $"{id} Chapter: {chapter}";
} }
protected override IEnumerable<Job> ExecuteReturnSubTasksInternal() protected override IEnumerable<Job> ExecuteReturnSubTasksInternal(JobBoss jobBoss)
{ {
Task downloadTask = new(delegate Task downloadTask = new(delegate
{ {

View File

@ -4,18 +4,18 @@ namespace Tranga.Jobs;
public class DownloadNewChapters : Job public class DownloadNewChapters : Job
{ {
public Manga manga { get; init; } public Manga manga { get; set; }
public string translatedLanguage { get; init; } public string translatedLanguage { get; init; }
public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Manga manga, DateTime lastExecution, public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Manga manga, DateTime lastExecution,
bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null, string translatedLanguage = "en") : base(clone, connector, lastExecution, recurring, bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null, string translatedLanguage = "en") : base(clone, JobType.DownloadNewChaptersJob, connector, lastExecution, recurring,
recurrence, parentJobId) recurrence, parentJobId)
{ {
this.manga = manga; this.manga = manga;
this.translatedLanguage = translatedLanguage; this.translatedLanguage = translatedLanguage;
} }
public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Manga manga, bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null, string translatedLanguage = "en") : base (clone, connector, recurring, recurrence, parentJobId) public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Manga manga, bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null, string translatedLanguage = "en") : base (clone, JobType.DownloadNewChaptersJob, connector, recurring, recurrence, parentJobId)
{ {
this.manga = manga; this.manga = manga;
this.translatedLanguage = translatedLanguage; this.translatedLanguage = translatedLanguage;
@ -31,8 +31,9 @@ public class DownloadNewChapters : Job
return $"{id} Manga: {manga}"; return $"{id} Manga: {manga}";
} }
protected override IEnumerable<Job> ExecuteReturnSubTasksInternal() protected override IEnumerable<Job> ExecuteReturnSubTasksInternal(JobBoss jobBoss)
{ {
manga.SaveSeriesInfoJson(settings.downloadLocation);
Chapter[] chapters = mangaConnector.GetNewChapters(manga, this.translatedLanguage); Chapter[] chapters = mangaConnector.GetNewChapters(manga, this.translatedLanguage);
this.progressToken.increments = chapters.Length; this.progressToken.increments = chapters.Length;
List<Job> jobs = new(); List<Job> jobs = new();
@ -42,6 +43,8 @@ public class DownloadNewChapters : Job
DownloadChapter downloadChapterJob = new(this, this.mangaConnector, chapter, parentJobId: this.id); DownloadChapter downloadChapterJob = new(this, this.mangaConnector, chapter, parentJobId: this.id);
jobs.Add(downloadChapterJob); jobs.Add(downloadChapterJob);
} }
UpdateMetadata updateMetadataJob = new(this, this.mangaConnector, this.manga, parentJobId: this.id);
jobs.Add(updateMetadataJob);
progressToken.Complete(); progressToken.Complete();
return jobs; return jobs;
} }

View File

@ -13,9 +13,13 @@ public abstract class Job : GlobalBase
public string id => GetId(); public string id => GetId();
internal IEnumerable<Job>? subJobs { get; private set; } internal IEnumerable<Job>? subJobs { get; private set; }
public string? parentJobId { get; init; } public string? parentJobId { get; init; }
public enum JobType : byte { DownloadChapterJob, DownloadNewChaptersJob, UpdateMetaDataJob }
internal Job(GlobalBase clone, MangaConnector connector, bool recurring = false, TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone) public JobType jobType;
internal Job(GlobalBase clone, JobType jobType, MangaConnector connector, bool recurring = false, TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone)
{ {
this.jobType = jobType;
this.mangaConnector = connector; this.mangaConnector = connector;
this.progressToken = new ProgressToken(0); this.progressToken = new ProgressToken(0);
this.recurring = recurring; this.recurring = recurring;
@ -27,9 +31,10 @@ public abstract class Job : GlobalBase
this.parentJobId = parentJobId; this.parentJobId = parentJobId;
} }
internal Job(GlobalBase clone, MangaConnector connector, DateTime lastExecution, bool recurring = false, internal Job(GlobalBase clone, JobType jobType, MangaConnector connector, DateTime lastExecution, bool recurring = false,
TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone) TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone)
{ {
this.jobType = jobType;
this.mangaConnector = connector; this.mangaConnector = connector;
this.progressToken = new ProgressToken(0); this.progressToken = new ProgressToken(0);
this.recurring = recurring; this.recurring = recurring;
@ -81,13 +86,13 @@ public abstract class Job : GlobalBase
subJob.Cancel(); subJob.Cancel();
} }
public IEnumerable<Job> ExecuteReturnSubTasks() public IEnumerable<Job> ExecuteReturnSubTasks(JobBoss jobBoss)
{ {
progressToken.Start(); progressToken.Start();
subJobs = ExecuteReturnSubTasksInternal(); subJobs = ExecuteReturnSubTasksInternal(jobBoss);
lastExecution = DateTime.Now; lastExecution = DateTime.Now;
return subJobs; return subJobs;
} }
protected abstract IEnumerable<Job> ExecuteReturnSubTasksInternal(); protected abstract IEnumerable<Job> ExecuteReturnSubTasksInternal(JobBoss jobBoss);
} }

View File

@ -253,7 +253,7 @@ public class JobBoss : GlobalBase
Log($"Next job in {jobs.MinBy(job => job.nextExecution)?.nextExecution.Subtract(DateTime.Now)} {jobs.MinBy(job => job.nextExecution)?.id}"); Log($"Next job in {jobs.MinBy(job => job.nextExecution)?.nextExecution.Subtract(DateTime.Now)} {jobs.MinBy(job => job.nextExecution)?.id}");
}else if (queueHead.progressToken.state is ProgressToken.State.Standby) }else if (queueHead.progressToken.state is ProgressToken.State.Standby)
{ {
Job[] subJobs = jobQueue.Peek().ExecuteReturnSubTasks().ToArray(); Job[] subJobs = jobQueue.Peek().ExecuteReturnSubTasks(this).ToArray();
AddJobs(subJobs); AddJobs(subJobs);
AddJobsToQueue(subJobs); AddJobsToQueue(subJobs);
}else if (queueHead.progressToken.state is ProgressToken.State.Running && DateTime.Now.Subtract(queueHead.progressToken.lastUpdate) > TimeSpan.FromMinutes(5)) }else if (queueHead.progressToken.state is ProgressToken.State.Running && DateTime.Now.Subtract(queueHead.progressToken.lastUpdate) > TimeSpan.FromMinutes(5))

View File

@ -23,7 +23,20 @@ public class JobJsonConverter : JsonConverter
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{ {
JObject jo = JObject.Load(reader); JObject jo = JObject.Load(reader);
if (jo.ContainsKey("manga"))//DownloadNewChapters
if (jo.ContainsKey("jobType") && jo["jobType"]!.Value<byte>() == (byte)Job.JobType.UpdateMetaDataJob)
{
return new UpdateMetadata(this._clone,
jo.GetValue("mangaConnector")!.ToObject<MangaConnector>(JsonSerializer.Create(new JsonSerializerSettings()
{
Converters =
{
this._mangaConnectorJsonConverter
}
}))!,
jo.GetValue("manga")!.ToObject<Manga>(),
jo.GetValue("parentJobId")!.Value<string?>());
}else if ((jo.ContainsKey("jobType") && jo["jobType"]!.Value<byte>() == (byte)Job.JobType.DownloadNewChaptersJob) || jo.ContainsKey("translatedLanguage"))//TODO change to jobType
{ {
return new DownloadNewChapters(this._clone, return new DownloadNewChapters(this._clone,
jo.GetValue("mangaConnector")!.ToObject<MangaConnector>(JsonSerializer.Create(new JsonSerializerSettings() jo.GetValue("mangaConnector")!.ToObject<MangaConnector>(JsonSerializer.Create(new JsonSerializerSettings()
@ -38,9 +51,7 @@ public class JobJsonConverter : JsonConverter
jo.GetValue("recurring")!.Value<bool>(), jo.GetValue("recurring")!.Value<bool>(),
jo.GetValue("recurrenceTime")!.ToObject<TimeSpan?>(), jo.GetValue("recurrenceTime")!.ToObject<TimeSpan?>(),
jo.GetValue("parentJobId")!.Value<string?>()); jo.GetValue("parentJobId")!.Value<string?>());
} }else if ((jo.ContainsKey("jobType") && jo["jobType"]!.Value<byte>() == (byte)Job.JobType.DownloadChapterJob) || jo.ContainsKey("chapter"))//TODO change to jobType
if (jo.ContainsKey("chapter"))//DownloadChapter
{ {
return new DownloadChapter(this._clone, return new DownloadChapter(this._clone,
jo.GetValue("mangaConnector")!.ToObject<MangaConnector>(JsonSerializer.Create(new JsonSerializerSettings() jo.GetValue("mangaConnector")!.ToObject<MangaConnector>(JsonSerializer.Create(new JsonSerializerSettings()

View File

@ -0,0 +1,50 @@
using Tranga.MangaConnectors;
namespace Tranga.Jobs;
public class UpdateMetadata : Job
{
public Manga manga { get; set; }
public UpdateMetadata(GlobalBase clone, MangaConnector connector, Manga manga, string? parentJobId = null) : base(clone, JobType.UpdateMetaDataJob, connector, parentJobId: parentJobId)
{
this.manga = manga;
}
protected override string GetId()
{
return $"{GetType()}-{manga.internalId}";
}
public override string ToString()
{
return $"{id} Manga: {manga}";
}
protected override IEnumerable<Job> ExecuteReturnSubTasksInternal(JobBoss jobBoss)
{
Manga? possibleUpdatedManga = mangaConnector.GetMangaFromId(manga.publicationId);
if (possibleUpdatedManga is { } updatedManga)
{
if(updatedManga.Equals(this.manga))
return Array.Empty<Job>();
this.manga.UpdateMetadata(updatedManga);
this.manga.SaveSeriesInfoJson(settings.downloadLocation, true);
if (parentJobId is not null && jobBoss.GetJobById(this.parentJobId) is DownloadNewChapters dncJob)
{
dncJob.manga = updatedManga;
}
this.progressToken.Complete();
}
else
{
Log($"Could not find Manga {manga}");
this.progressToken.Cancel();
return Array.Empty<Job>();
}
this.progressToken.Cancel();
return Array.Empty<Job>();
}
}

View File

@ -41,15 +41,33 @@ public abstract class LibraryConnector : GlobalBase
Method = HttpMethod.Get, Method = HttpMethod.Get,
RequestUri = new Uri(url) RequestUri = new Uri(url)
}; };
HttpResponseMessage response = client.Send(requestMessage); try
logger?.WriteLine("LibraryManager.NetClient", $"GET {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}"); {
if(response.StatusCode is HttpStatusCode.Unauthorized && response.RequestMessage!.RequestUri!.AbsoluteUri != url) HttpResponseMessage response = client.Send(requestMessage);
return MakeRequest(response.RequestMessage!.RequestUri!.AbsoluteUri, authScheme, auth, logger); logger?.WriteLine("LibraryManager.NetClient",
else if (response.IsSuccessStatusCode) $"GET {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}");
return response.Content.ReadAsStream();
else if (response.StatusCode is HttpStatusCode.Unauthorized &&
response.RequestMessage!.RequestUri!.AbsoluteUri != url)
return MakeRequest(response.RequestMessage!.RequestUri!.AbsoluteUri, authScheme, auth, logger);
else if (response.IsSuccessStatusCode)
return response.Content.ReadAsStream();
else
return Stream.Null;
}
catch (Exception e)
{
switch (e)
{
case HttpRequestException:
logger?.WriteLine("LibraryManager.NetClient", $"Failed to make Request:\n\r{e}\n\rContinuing.");
break;
default:
throw;
}
return Stream.Null; return Stream.Null;
}
} }
public static bool MakePost(string url, string authScheme, string auth, Logger? logger) public static bool MakePost(string url, string authScheme, string auth, Logger? logger)

View File

@ -11,23 +11,32 @@ namespace Tranga;
/// </summary> /// </summary>
public struct Manga public struct Manga
{ {
public string sortName { get; } public string sortName { get; private set; }
public List<string> authors { get; } public List<string> authors { get; }
// ReSharper disable once UnusedAutoPropertyAccessor.Global // ReSharper disable once UnusedAutoPropertyAccessor.Global
public Dictionary<string,string> altTitles { get; } public Dictionary<string,string> altTitles { get; }
// ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once MemberCanBePrivate.Global
public string? description { get; } public string? description { get; private set; }
public string[] tags { get; } public string[] tags { get; }
// ReSharper disable once UnusedAutoPropertyAccessor.Global // ReSharper disable once UnusedAutoPropertyAccessor.Global
public string? coverUrl { get; } public string? coverUrl { get; }
public string? coverFileNameInCache { get; set; } public string? coverFileNameInCache { get; }
// ReSharper disable once UnusedAutoPropertyAccessor.Global // ReSharper disable once UnusedAutoPropertyAccessor.Global
public Dictionary<string,string> links { get; } public Dictionary<string,string> links { get; }
// ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once MemberCanBePrivate.Global
public int? year { get; } public int? year { get; private set; }
public string? originalLanguage { get; } public string? originalLanguage { get; }
// ReSharper disable twice MemberCanBePrivate.Global // ReSharper disable twice MemberCanBePrivate.Global
public string status { get; } public string status { get; private set; }
public ReleaseStatusByte releaseStatus { get; }
public enum ReleaseStatusByte : byte
{
Continuing = 0,
Completed = 1,
OnHiatus = 2,
Cancelled = 3,
Unreleased = 4
};
public string folderName { get; private set; } public string folderName { get; private set; }
public string publicationId { get; } public string publicationId { get; }
public string internalId { get; } public string internalId { get; }
@ -38,7 +47,7 @@ public struct Manga
private static readonly Regex LegalCharacters = new (@"[A-Z]*[a-z]*[0-9]* *\.*-*,*'*\'*\)*\(*~*!*"); private static readonly Regex LegalCharacters = new (@"[A-Z]*[a-z]*[0-9]* *\.*-*,*'*\'*\)*\(*~*!*");
[JsonConstructor] [JsonConstructor]
public Manga(string sortName, List<string> authors, string? description, Dictionary<string,string> altTitles, string[] tags, string? coverUrl, string? coverFileNameInCache, Dictionary<string,string>? links, int? year, string? originalLanguage, string status, string publicationId, string? folderName = null, float? ignoreChaptersBelow = 0) public Manga(string sortName, List<string> authors, string? description, Dictionary<string,string> altTitles, string[] tags, string? coverUrl, string? coverFileNameInCache, Dictionary<string,string>? links, int? year, string? originalLanguage, string status, string publicationId, ReleaseStatusByte releaseStatus = 0, string? websiteUrl = null, string? folderName = null, float? ignoreChaptersBelow = 0)
{ {
this.sortName = sortName; this.sortName = sortName;
this.authors = authors; this.authors = authors;
@ -60,6 +69,31 @@ public struct Manga
this.ignoreChaptersBelow = ignoreChaptersBelow ?? 0f; this.ignoreChaptersBelow = ignoreChaptersBelow ?? 0f;
this.latestChapterDownloaded = 0; this.latestChapterDownloaded = 0;
this.latestChapterAvailable = 0; this.latestChapterAvailable = 0;
this.releaseStatus = releaseStatus;
}
public void UpdateMetadata(Manga newManga)
{
this.sortName = newManga.sortName;
this.description = newManga.description;
foreach (string author in newManga.authors)
if(this.authors.Contains(author))
this.authors.Add(author);
this.status = newManga.status;
this.year = newManga.year;
}
public override bool Equals(object? obj)
{
if (obj is not Manga compareManga)
return false;
return this.description == compareManga.description &&
this.year == compareManga.year &&
this.status == compareManga.status &&
this.releaseStatus == compareManga.releaseStatus &&
this.sortName == compareManga.sortName &&
this.latestChapterAvailable.Equals(compareManga.latestChapterAvailable) &&
this.tags.Equals(compareManga.tags);
} }
public override string ToString() public override string ToString()
@ -92,11 +126,11 @@ public struct Manga
latestChapterDownloaded = latestChapterDownloaded < chapterNumber ? chapterNumber : latestChapterDownloaded; latestChapterDownloaded = latestChapterDownloaded < chapterNumber ? chapterNumber : latestChapterDownloaded;
} }
public void SaveSeriesInfoJson(string downloadDirectory) public void SaveSeriesInfoJson(string downloadDirectory, bool overwrite = false)
{ {
string publicationFolder = CreatePublicationFolder(downloadDirectory); string publicationFolder = CreatePublicationFolder(downloadDirectory);
string seriesInfoPath = Path.Join(publicationFolder, "series.json"); string seriesInfoPath = Path.Join(publicationFolder, "series.json");
if(!File.Exists(seriesInfoPath)) if(overwrite || (!overwrite && !File.Exists(seriesInfoPath)))
File.WriteAllText(seriesInfoPath,this.GetSeriesInfoJson()); File.WriteAllText(seriesInfoPath,this.GetSeriesInfoJson());
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
File.SetUnixFileMode(seriesInfoPath, GroupRead | GroupWrite | OtherRead | OtherWrite | UserRead | UserWrite); File.SetUnixFileMode(seriesInfoPath, GroupRead | GroupWrite | OtherRead | OtherWrite | UserRead | UserWrite);
@ -105,7 +139,7 @@ public struct Manga
/// <returns>Serialized JSON String for series.json</returns> /// <returns>Serialized JSON String for series.json</returns>
private string GetSeriesInfoJson() private string GetSeriesInfoJson()
{ {
SeriesInfo si = new (new Metadata(this.sortName, this.year.ToString() ?? string.Empty, this.status, this.description ?? "")); SeriesInfo si = new (new Metadata(this));
return System.Text.Json.JsonSerializer.Serialize(si); return System.Text.Json.JsonSerializer.Serialize(si);
} }
@ -151,6 +185,11 @@ public struct Manga
"droppato" "droppato"
}; };
public Metadata(Manga manga) : this(manga.sortName, manga.year.ToString() ?? string.Empty, manga.status, manga.description ?? "")
{
}
public Metadata(string name, string year, string status, string description_text) public Metadata(string name, string year, string status, string description_text)
{ {
this.name = name; this.name = name;

View File

@ -36,6 +36,11 @@ public class Bato : MangaConnector
return publications; return publications;
} }
public override Manga? GetMangaFromId(string publicationId)
{
return GetMangaFromUrl($"https://bato.to/title/{publicationId}");
}
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromUrl(string url)
{ {
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
@ -102,9 +107,18 @@ public class Bato : MangaConnector
string status = document.DocumentNode.SelectSingleNode("//span[text()='Original Publication:']/..") string status = document.DocumentNode.SelectSingleNode("//span[text()='Original Publication:']/..")
.ChildNodes[2].InnerText; .ChildNodes[2].InnerText;
Manga.ReleaseStatusByte releaseStatus = Manga.ReleaseStatusByte.Unreleased;
switch (status.ToLower())
{
case "ongoing": releaseStatus = Manga.ReleaseStatusByte.Continuing; break;
case "completed": releaseStatus = Manga.ReleaseStatusByte.Completed; break;
case "hiatus": releaseStatus = Manga.ReleaseStatusByte.OnHiatus; break;
case "cancelled": releaseStatus = Manga.ReleaseStatusByte.Cancelled; break;
case "pending": releaseStatus = Manga.ReleaseStatusByte.Unreleased; break;
}
Manga manga = new (sortName, authors, description, altTitles, tags, posterUrl, coverFileNameInCache, new Dictionary<string, string>(), Manga manga = new (sortName, authors, description, altTitles, tags, posterUrl, coverFileNameInCache, new Dictionary<string, string>(),
year, originalLanguage, status, publicationId); year, originalLanguage, status, publicationId, releaseStatus);
cachedPublications.Add(manga); cachedPublications.Add(manga);
return manga; return manga;
} }

View File

@ -38,6 +38,8 @@ public abstract class MangaConnector : GlobalBase
public abstract Manga? GetMangaFromUrl(string url); public abstract Manga? GetMangaFromUrl(string url);
public abstract Manga? GetMangaFromId(string publicationId);
/// <summary> /// <summary>
/// Returns all Chapters of the publication in the provided language. /// Returns all Chapters of the publication in the provided language.
/// If the language is empty or null, returns all Chapters in all Languages. /// If the language is empty or null, returns all Chapters in all Languages.
@ -148,7 +150,8 @@ public abstract class MangaConnector : GlobalBase
/// Copies the already downloaded cover from cache to downloadLocation /// Copies the already downloaded cover from cache to downloadLocation
/// </summary> /// </summary>
/// <param name="manga">Publication to retrieve Cover for</param> /// <param name="manga">Publication to retrieve Cover for</param>
public void CopyCoverFromCacheToDownloadLocation(Manga manga) /// <param name="retries">Number of times to retry to copy the cover (or download it first)</param>
public void CopyCoverFromCacheToDownloadLocation(Manga manga, int? retries = 1)
{ {
Log($"Copy cover {manga}"); Log($"Copy cover {manga}");
//Check if Publication already has a Folder and cover //Check if Publication already has a Folder and cover
@ -161,6 +164,18 @@ public abstract class MangaConnector : GlobalBase
} }
string fileInCache = Path.Join(settings.coverImageCache, manga.coverFileNameInCache); string fileInCache = Path.Join(settings.coverImageCache, manga.coverFileNameInCache);
if (!File.Exists(fileInCache))
{
Log($"Cloníng cover failed: File missing {fileInCache}.");
if (retries > 0 && manga.coverUrl is not null)
{
Log($"Trying {retries} more times");
SaveCoverImageToCache(manga.coverUrl, 0);
CopyCoverFromCacheToDownloadLocation(manga, --retries);
}
return;
}
string newFilePath = Path.Join(publicationFolder, $"cover.{Path.GetFileName(fileInCache).Split('.')[^1]}" ); string newFilePath = Path.Join(publicationFolder, $"cover.{Path.GetFileName(fileInCache).Split('.')[^1]}" );
Log($"Cloning cover {fileInCache} -> {newFilePath}"); Log($"Cloning cover {fileInCache} -> {newFilePath}");
File.Copy(fileInCache, newFilePath, true); File.Copy(fileInCache, newFilePath, true);

View File

@ -72,13 +72,10 @@ public class MangaDex : MangaConnector
return retManga.ToArray(); return retManga.ToArray();
} }
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromId(string publicationId)
{ {
Regex idRex = new (@"https:\/\/mangadex.org\/title\/([A-z0-9-]*)\/.*");
string id = idRex.Match(url).Groups[1].Value;
Log($"Got id {id} from {url}");
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest($"https://api.mangadex.org/manga/{id}", (byte)RequestType.Manga); downloadClient.MakeRequest($"https://api.mangadex.org/manga/{publicationId}", (byte)RequestType.Manga);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return null; return null;
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result); JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
@ -87,6 +84,14 @@ public class MangaDex : MangaConnector
return null; return null;
} }
public override Manga? GetMangaFromUrl(string url)
{
Regex idRex = new (@"https:\/\/mangadex.org\/title\/([A-z0-9-]*)\/.*");
string id = idRex.Match(url).Groups[1].Value;
Log($"Got id {id} from {url}");
return GetMangaFromId(id);
}
private Manga? MangaFromJsonObject(JsonObject manga) private Manga? MangaFromJsonObject(JsonObject manga)
{ {
if (!manga.ContainsKey("attributes")) if (!manga.ContainsKey("attributes"))
@ -170,6 +175,14 @@ public class MangaDex : MangaConnector
if(!attributes.ContainsKey("status")) if(!attributes.ContainsKey("status"))
return null; return null;
string status = attributes["status"]!.GetValue<string>(); string status = attributes["status"]!.GetValue<string>();
Manga.ReleaseStatusByte releaseStatus = Manga.ReleaseStatusByte.Unreleased;
switch (status.ToLower())
{
case "ongoing": releaseStatus = Manga.ReleaseStatusByte.Continuing; break;
case "completed": releaseStatus = Manga.ReleaseStatusByte.Completed; break;
case "hiatus": releaseStatus = Manga.ReleaseStatusByte.OnHiatus; break;
case "cancelled": releaseStatus = Manga.ReleaseStatusByte.Cancelled; break;
}
Manga pub = new( Manga pub = new(
title, title,
@ -183,7 +196,8 @@ public class MangaDex : MangaConnector
year, year,
originalLanguage, originalLanguage,
status, status,
publicationId publicationId,
releaseStatus
); );
cachedPublications.Add(pub); cachedPublications.Add(pub);
return pub; return pub;

View File

@ -39,6 +39,11 @@ public class MangaKatana : MangaConnector
return publications; return publications;
} }
public override Manga? GetMangaFromId(string publicationId)
{
return GetMangaFromUrl($"https://mangakatana.com/manga/{publicationId}");
}
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromUrl(string url)
{ {
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
@ -87,6 +92,7 @@ public class MangaKatana : MangaConnector
HashSet<string> tags = new(); HashSet<string> tags = new();
string[] authors = Array.Empty<string>(); string[] authors = Array.Empty<string>();
string originalLanguage = ""; string originalLanguage = "";
Manga.ReleaseStatusByte releaseStatus = Manga.ReleaseStatusByte.Unreleased;
HtmlNode infoNode = document.DocumentNode.SelectSingleNode("//*[@id='single_book']"); HtmlNode infoNode = document.DocumentNode.SelectSingleNode("//*[@id='single_book']");
string sortName = infoNode.Descendants("h1").First(n => n.HasClass("heading")).InnerText; string sortName = infoNode.Descendants("h1").First(n => n.HasClass("heading")).InnerText;
@ -110,6 +116,11 @@ public class MangaKatana : MangaConnector
break; break;
case "status": case "status":
status = value; status = value;
switch (status.ToLower())
{
case "ongoing": releaseStatus = Manga.ReleaseStatusByte.Continuing; break;
case "completed": releaseStatus = Manga.ReleaseStatusByte.Completed; break;
}
break; break;
case "genres": case "genres":
tags = row.SelectNodes("div").Last().Descendants("a").Select(a => a.InnerText).ToHashSet(); tags = row.SelectNodes("div").Last().Descendants("a").Select(a => a.InnerText).ToHashSet();
@ -136,7 +147,7 @@ public class MangaKatana : MangaConnector
} }
Manga manga = new (sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links, Manga manga = new (sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links,
year, originalLanguage, status, publicationId); year, originalLanguage, status, publicationId, releaseStatus);
cachedPublications.Add(manga); cachedPublications.Add(manga);
return manga; return manga;
} }

View File

@ -32,6 +32,11 @@ public class MangaLife : MangaConnector
return publications; return publications;
} }
public override Manga? GetMangaFromId(string publicationId)
{
return GetMangaFromUrl($"https://manga4life.com/manga/{publicationId}");
}
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromUrl(string url)
{ {
Regex publicationIdRex = new(@"https:\/\/manga4life.com\/manga\/(.*)(\/.*)*"); Regex publicationIdRex = new(@"https:\/\/manga4life.com\/manga\/(.*)(\/.*)*");
@ -72,6 +77,7 @@ public class MangaLife : MangaConnector
string originalLanguage = "", status = ""; string originalLanguage = "", status = "";
Dictionary<string, string> altTitles = new(), links = new(); Dictionary<string, string> altTitles = new(), links = new();
HashSet<string> tags = new(); HashSet<string> tags = new();
Manga.ReleaseStatusByte releaseStatus = Manga.ReleaseStatusByte.Unreleased;
HtmlNode posterNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']//div[@class='row']//img"); HtmlNode posterNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']//div[@class='row']//img");
string posterUrl = posterNode.GetAttributeValue("src", ""); string posterUrl = posterNode.GetAttributeValue("src", "");
@ -104,6 +110,14 @@ public class MangaLife : MangaConnector
foreach (HtmlNode statusNode in statusNodes) foreach (HtmlNode statusNode in statusNodes)
if (statusNode.InnerText.Contains("publish", StringComparison.CurrentCultureIgnoreCase)) if (statusNode.InnerText.Contains("publish", StringComparison.CurrentCultureIgnoreCase))
status = statusNode.InnerText.Split(' ')[0]; status = statusNode.InnerText.Split(' ')[0];
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 HtmlNode descriptionNode = document.DocumentNode
.SelectNodes("//div[@class='BoxBody']//div[@class='row']//span[text()='Description:']/..") .SelectNodes("//div[@class='BoxBody']//div[@class='row']//span[text()='Description:']/..")
@ -111,8 +125,7 @@ public class MangaLife : MangaConnector
string description = descriptionNode.InnerText; string description = descriptionNode.InnerText;
Manga manga = new(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, Manga manga = new(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl,
coverFileNameInCache, links, coverFileNameInCache, links, year, originalLanguage, status, publicationId, releaseStatus);
year, originalLanguage, status, publicationId);
cachedPublications.Add(manga); cachedPublications.Add(manga);
return manga; return manga;
} }

View File

@ -54,6 +54,11 @@ public class Manganato : MangaConnector
return ret.ToArray(); return ret.ToArray();
} }
public override Manga? GetMangaFromId(string publicationId)
{
return GetMangaFromUrl($"https://chapmanganato.com/{publicationId}");
}
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromUrl(string url)
{ {
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
@ -74,6 +79,7 @@ public class Manganato : MangaConnector
HashSet<string> tags = new(); HashSet<string> tags = new();
string[] authors = Array.Empty<string>(); string[] authors = Array.Empty<string>();
string originalLanguage = ""; string originalLanguage = "";
Manga.ReleaseStatusByte releaseStatus = Manga.ReleaseStatusByte.Unreleased;
HtmlNode infoNode = document.DocumentNode.Descendants("div").First(d => d.HasClass("story-info-right")); HtmlNode infoNode = document.DocumentNode.Descendants("div").First(d => d.HasClass("story-info-right"));
@ -99,6 +105,11 @@ public class Manganato : MangaConnector
break; break;
case "status": case "status":
status = value; status = value;
switch (status.ToLower())
{
case "ongoing": releaseStatus = Manga.ReleaseStatusByte.Continuing; break;
case "completed": releaseStatus = Manga.ReleaseStatusByte.Completed; break;
}
break; break;
case "genres": case "genres":
string[] genres = value.Split(" - "); string[] genres = value.Split(" - ");
@ -122,7 +133,7 @@ public class Manganato : MangaConnector
int year = Convert.ToInt32(yearString.Split(',')[^1]) + 2000; int year = Convert.ToInt32(yearString.Split(',')[^1]) + 2000;
Manga manga = new (sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links, Manga manga = new (sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links,
year, originalLanguage, status, publicationId); year, originalLanguage, status, publicationId, releaseStatus);
cachedPublications.Add(manga); cachedPublications.Add(manga);
return manga; return manga;
} }
@ -156,18 +167,18 @@ public class Manganato : MangaConnector
HtmlNode chapterList = document.DocumentNode.Descendants("ul").First(l => l.HasClass("row-content-chapter")); HtmlNode chapterList = document.DocumentNode.Descendants("ul").First(l => l.HasClass("row-content-chapter"));
Regex volRex = new(@"Vol\.([0-9]+).*"); Regex volRex = new(@"Vol\.([0-9]+).*");
Regex chapterRex = new(@"Chapter ([0-9]+(\.[0-9]+)*){1}.*"); Regex chapterRex = new(@"https:\/\/chapmanganato.com/manga-[A-z0-9]+\/chapter-([0-9\.]+)");
Regex nameRex = new(@"Chapter ([0-9]+(\.[0-9]+)*){1}:? (.*)"); Regex nameRex = new(@"Chapter ([0-9]+(\.[0-9]+)*){1}:? (.*)");
foreach (HtmlNode chapterInfo in chapterList.Descendants("li")) foreach (HtmlNode chapterInfo in chapterList.Descendants("li"))
{ {
string fullString = chapterInfo.Descendants("a").First(d => d.HasClass("chapter-name")).InnerText; string fullString = chapterInfo.Descendants("a").First(d => d.HasClass("chapter-name")).InnerText;
string? volumeNumber = volRex.IsMatch(fullString) ? volRex.Match(fullString).Groups[1].Value : null;
string chapterNumber = chapterRex.IsMatch(fullString) ? chapterRex.Match(fullString).Groups[1].Value : fullString;
string chapterName = nameRex.Match(fullString).Groups[3].Value;
string url = chapterInfo.Descendants("a").First(d => d.HasClass("chapter-name")) string url = chapterInfo.Descendants("a").First(d => d.HasClass("chapter-name"))
.GetAttributeValue("href", ""); .GetAttributeValue("href", "");
string? volumeNumber = volRex.IsMatch(fullString) ? volRex.Match(fullString).Groups[1].Value : null;
string chapterNumber = chapterRex.Match(url).Groups[1].Value;
string chapterName = nameRex.Match(fullString).Groups[3].Value;
ret.Add(new Chapter(manga, chapterName, volumeNumber, chapterNumber, url)); ret.Add(new Chapter(manga, chapterName, volumeNumber, chapterNumber, url));
} }
ret.Reverse(); ret.Reverse();

View File

@ -1,7 +1,9 @@
using System.Net; using System.Data;
using System.Net;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Xml.Linq; using System.Xml.Linq;
using HtmlAgilityPack; using HtmlAgilityPack;
using Newtonsoft.Json;
using Tranga.Jobs; using Tranga.Jobs;
namespace Tranga.MangaConnectors; namespace Tranga.MangaConnectors;
@ -16,21 +18,102 @@ public class Mangasee : MangaConnector
}); });
} }
private struct SearchResult
{
public string i { get; set; }
public string s { get; set; }
public string[] a { get; set; }
}
public override Manga[] GetManga(string publicationTitle = "") public override Manga[] GetManga(string publicationTitle = "")
{ {
Log($"Searching Publications. Term=\"{publicationTitle}\""); Log($"Searching Publications. Term=\"{publicationTitle}\"");
string sanitizedTitle = WebUtility.UrlEncode(publicationTitle); string requestUrl = "https://mangasee123.com/_search.php";
string requestUrl = $"https://mangasee123.com/search/?name={sanitizedTitle}";
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest(requestUrl, 1); downloadClient.MakeRequest(requestUrl, 1);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
{
Log($"Failed to retrieve search: {requestResult.statusCode}");
return Array.Empty<Manga>(); return Array.Empty<Manga>();
}
if (requestResult.htmlDocument is null) try
{
SearchResult[] searchResults = JsonConvert.DeserializeObject<SearchResult[]>(requestResult.htmlDocument!.DocumentNode.InnerText) ??
throw new NoNullAllowedException();
SearchResult[] filteredResults = FilteredResults(publicationTitle, searchResults);
Log($"Total available manga: {searchResults.Length} Filtered down to: {filteredResults.Length}");
/*
Dictionary<SearchResult, int> levenshteinRelation = filteredResults.ToDictionary(result => result,
result =>
{
Log($"Levenshtein {result.s}");
return LevenshteinDistance(publicationTitle.Replace(" ", "").ToLower(), result.s.Replace(" ", "").ToLower());
});
Log($"After levenshtein: {levenshteinRelation.Count}");*/
string[] urls = filteredResults.Select(result => $"https://mangasee123.com/manga/{result.i}").ToArray();
List<Manga> searchResultManga = new();
foreach (string url in urls)
{
Manga? newManga = GetMangaFromUrl(url);
if(newManga is { } manga)
searchResultManga.Add(manga);
}
Log($"Retrieved {searchResultManga.Count} publications. Term=\"{publicationTitle}\"");
return searchResultManga.ToArray();
}
catch (NoNullAllowedException)
{
Log("Failed to retrieve search");
return Array.Empty<Manga>(); return Array.Empty<Manga>();
Manga[] publications = ParsePublicationsFromHtml(requestResult.htmlDocument); }
Log($"Retrieved {publications.Length} publications. Term=\"{publicationTitle}\""); }
return publications;
private SearchResult[] FilteredResults(string publicationTitle, SearchResult[] unfilteredSearchResults)
{
string[] bannedStrings = {"a", "the", "of", "as", "to", "no", "for", "on", "with", "be", "and", "in", "wa", "at"};
string[] cleanSplitPublicationTitle = publicationTitle.Split(' ')
.Where(part => part.Length > 0 && !bannedStrings.Contains(part.ToLower())).ToArray();
return unfilteredSearchResults.Where(usr =>
{
string cleanSearchResultString = string.Join(' ', usr.s.Split(' ').Where(part => part.Length > 0 && !bannedStrings.Contains(part.ToLower())));
foreach(string splitPublicationTitlePart in cleanSplitPublicationTitle)
if (cleanSearchResultString.Contains(splitPublicationTitlePart, StringComparison.InvariantCultureIgnoreCase) ||
cleanSearchResultString.Contains(splitPublicationTitlePart, StringComparison.InvariantCultureIgnoreCase))
return true;
return false;
}).ToArray();
}
private int LevenshteinDistance(string a, string b)
{
if (b.Length == 0)
return a.Length;
if (a.Length == 0)
return b.Length;
if (a[0] == b[0])
return LevenshteinDistance(a[1..], b[1..]);
int case1 = LevenshteinDistance(a, b[1..]);
int case2 = LevenshteinDistance(a[1..], b[1..]);
int case3 = LevenshteinDistance(a[1..], b);
if (case1 < case2)
{
return 1 + (case1 < case3 ? case1 : case3);
}
else
{
return 1 + (case2 < case3 ? case2 : case3);
}
}
public override Manga? GetMangaFromId(string publicationId)
{
return GetMangaFromUrl($"https://mangasee123.com/manga/{publicationId}");
} }
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromUrl(string url)
@ -44,35 +127,12 @@ public class Mangasee : MangaConnector
return null; return null;
} }
private Manga[] ParsePublicationsFromHtml(HtmlDocument document)
{
HtmlNode resultsNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']/div[last()]/div[1]/div");
if (resultsNode.Descendants("div").Count() == 1 && resultsNode.Descendants("div").First().HasClass("NoResults"))
{
Log("No results.");
return Array.Empty<Manga>();
}
Log($"{resultsNode.SelectNodes("div").Count} items.");
HashSet<Manga> ret = new();
foreach (HtmlNode resultNode in resultsNode.SelectNodes("div"))
{
string url = resultNode.Descendants().First(d => d.HasClass("SeriesName")).GetAttributeValue("href", "");
Manga? manga = GetMangaFromUrl($"https://mangasee123.com{url}");
if (manga is not null)
ret.Add((Manga)manga);
}
return ret.ToArray();
}
private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId) private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId)
{ {
string originalLanguage = "", status = ""; string originalLanguage = "", status = "";
Dictionary<string, string> altTitles = new(), links = new(); Dictionary<string, string> altTitles = new(), links = new();
HashSet<string> tags = new(); HashSet<string> tags = new();
Manga.ReleaseStatusByte releaseStatus = Manga.ReleaseStatusByte.Unreleased;
HtmlNode posterNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']//div[@class='row']//img"); HtmlNode posterNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']//div[@class='row']//img");
string posterUrl = posterNode.GetAttributeValue("src", ""); string posterUrl = posterNode.GetAttributeValue("src", "");
@ -105,6 +165,14 @@ public class Mangasee : MangaConnector
foreach (HtmlNode statusNode in statusNodes) foreach (HtmlNode statusNode in statusNodes)
if (statusNode.InnerText.Contains("publish", StringComparison.CurrentCultureIgnoreCase)) if (statusNode.InnerText.Contains("publish", StringComparison.CurrentCultureIgnoreCase))
status = statusNode.InnerText.Split(' ')[0]; status = statusNode.InnerText.Split(' ')[0];
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 HtmlNode descriptionNode = document.DocumentNode
.SelectNodes("//div[@class='BoxBody']//div[@class='row']//span[text()='Description:']/..") .SelectNodes("//div[@class='BoxBody']//div[@class='row']//span[text()='Description:']/..")
@ -113,7 +181,7 @@ public class Mangasee : MangaConnector
Manga manga = new(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, Manga manga = new(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl,
coverFileNameInCache, links, coverFileNameInCache, links,
year, originalLanguage, status, publicationId); year, originalLanguage, status, publicationId, releaseStatus);
cachedPublications.Add(manga); cachedPublications.Add(manga);
return manga; return manga;
} }
@ -142,7 +210,7 @@ public class Mangasee : MangaConnector
} }
catch (HttpRequestException e) catch (HttpRequestException e)
{ {
Log($"Failed to load XML\n\r{e}"); Log($"Failed to load https://mangasee123.com/rss/{manga.publicationId}.xml \n\r{e}");
return Array.Empty<Chapter>(); return Array.Empty<Chapter>();
} }
} }

View File

@ -54,6 +54,11 @@ public class Mangaworld: MangaConnector
return ret.ToArray(); return ret.ToArray();
} }
public override Manga? GetMangaFromId(string publicationId)
{
return GetMangaFromUrl($"https://www.mangaworld.bz/manga/{publicationId}");
}
public override Manga? GetMangaFromUrl(string url) public override Manga? GetMangaFromUrl(string url)
{ {
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
@ -64,7 +69,9 @@ public class Mangaworld: MangaConnector
if (requestResult.htmlDocument is null) if (requestResult.htmlDocument is null)
return null; return null;
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, url.Split('/')[^2]); Regex idRex = new (@"https:\/\/www\.mangaworld\.bz\/manga\/([0-9]+\/[0-9A-z\-]+)");
string id = idRex.Match(url).Groups[1].Value;
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, id);
} }
private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId) private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId)
@ -72,6 +79,7 @@ public class Mangaworld: MangaConnector
Dictionary<string, string> altTitles = new(); Dictionary<string, string> altTitles = new();
Dictionary<string, string>? links = null; Dictionary<string, string>? links = null;
string originalLanguage = ""; string originalLanguage = "";
Manga.ReleaseStatusByte releaseStatus = Manga.ReleaseStatusByte.Unreleased;
HtmlNode infoNode = document.DocumentNode.Descendants("div").First(d => d.HasClass("info")); HtmlNode infoNode = document.DocumentNode.Descendants("div").First(d => d.HasClass("info"));
@ -94,6 +102,15 @@ public class Mangaworld: MangaConnector
string[] authors = new[] { authorsNode.SelectNodes("a").First().InnerText }; string[] authors = new[] { authorsNode.SelectNodes("a").First().InnerText };
string status = metadata.SelectSingleNode("//span[text()='Stato: ']/..").SelectNodes("a").First().InnerText; string status = metadata.SelectSingleNode("//span[text()='Stato: ']/..").SelectNodes("a").First().InnerText;
// ReSharper disable 5 times StringLiteralTypo
switch (status.ToLower())
{
case "cancellato": releaseStatus = Manga.ReleaseStatusByte.Cancelled; break;
case "in pausa": releaseStatus = Manga.ReleaseStatusByte.OnHiatus; break;
case "droppato": releaseStatus = Manga.ReleaseStatusByte.Cancelled; break;
case "finito": releaseStatus = Manga.ReleaseStatusByte.Completed; break;
case "in corso": releaseStatus = Manga.ReleaseStatusByte.Continuing; break;
}
string posterUrl = document.DocumentNode.SelectSingleNode("//img[@class='rounded']").GetAttributeValue("src", ""); string posterUrl = document.DocumentNode.SelectSingleNode("//img[@class='rounded']").GetAttributeValue("src", "");
@ -105,7 +122,7 @@ public class Mangaworld: MangaConnector
int year = Convert.ToInt32(yearString); int year = Convert.ToInt32(yearString);
Manga manga = new (sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links, Manga manga = new (sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links,
year, originalLanguage, status, publicationId); year, originalLanguage, status, publicationId, releaseStatus);
cachedPublications.Add(manga); cachedPublications.Add(manga);
return manga; return manga;
} }

View File

@ -304,6 +304,34 @@ public class Server : GlobalBase
_parent.jobBoss.AddJob(new DownloadNewChapters(this, connector!, manga, false, translatedLanguage: translatedLanguage??"en")); _parent.jobBoss.AddJob(new DownloadNewChapters(this, connector!, manga, false, translatedLanguage: translatedLanguage??"en"));
SendResponse(HttpStatusCode.Accepted, response); SendResponse(HttpStatusCode.Accepted, response);
break; break;
case "Jobs/UpdateMetadata":
if (!requestVariables.TryGetValue("internalId", out internalId))
{
foreach (Job pJob in _parent.jobBoss.jobs.Where(possibleDncJob =>
possibleDncJob.jobType is Job.JobType.DownloadNewChaptersJob).ToArray())//ToArray to avoid modyifying while adding new jobs
{
DownloadNewChapters dncJob = pJob as DownloadNewChapters ??
throw new Exception("Has to be DownloadNewChapters Job");
_parent.jobBoss.AddJob(new UpdateMetadata(this, dncJob.mangaConnector, dncJob.manga));
}
SendResponse(HttpStatusCode.Accepted, response);
}
else
{
Job[] possibleDncJobs = _parent.jobBoss.GetJobsLike(internalId: internalId).ToArray();
switch (possibleDncJobs.Length)
{
case <1: SendResponse(HttpStatusCode.BadRequest, response, "Could not find matching release"); break;
case >1: SendResponse(HttpStatusCode.BadRequest, response, "Multiple releases??"); break;
default:
DownloadNewChapters dncJob = possibleDncJobs[0] as DownloadNewChapters ??
throw new Exception("Has to be DownloadNewChapters Job");
_parent.jobBoss.AddJob(new UpdateMetadata(this, dncJob.mangaConnector, dncJob.manga));
SendResponse(HttpStatusCode.Accepted, response);
break;
}
}
break;
case "Jobs/StartNow": case "Jobs/StartNow":
if (!requestVariables.TryGetValue("jobId", out jobId) || if (!requestVariables.TryGetValue("jobId", out jobId) ||
!_parent.jobBoss.TryGetJobById(jobId, out job)) !_parent.jobBoss.TryGetJobById(jobId, out job))