Compare commits
No commits in common. "442d9493719a74b76e9726e2ed756561ba963af3" and "c965bc38d1a24441e8f2acfcd509554e75e51b43" have entirely different histories.
442d949371
...
c965bc38d1
@ -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, JobType.DownloadChapterJob, connector, lastExecution, parentJobId: parentJobId)
|
public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, DateTime lastExecution, string? parentJobId = null) : base(clone, connector, lastExecution, parentJobId: parentJobId)
|
||||||
{
|
{
|
||||||
this.chapter = chapter;
|
this.chapter = chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, string? parentJobId = null) : base(clone, JobType.DownloadChapterJob, connector, parentJobId: parentJobId)
|
public DownloadChapter(GlobalBase clone, MangaConnector connector, Chapter chapter, string? parentJobId = null) : base(clone, 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(JobBoss jobBoss)
|
protected override IEnumerable<Job> ExecuteReturnSubTasksInternal()
|
||||||
{
|
{
|
||||||
Task downloadTask = new(delegate
|
Task downloadTask = new(delegate
|
||||||
{
|
{
|
||||||
|
@ -4,18 +4,18 @@ namespace Tranga.Jobs;
|
|||||||
|
|
||||||
public class DownloadNewChapters : Job
|
public class DownloadNewChapters : Job
|
||||||
{
|
{
|
||||||
public Manga manga { get; set; }
|
public Manga manga { get; init; }
|
||||||
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, JobType.DownloadNewChaptersJob, connector, lastExecution, recurring,
|
bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null, string translatedLanguage = "en") : base(clone, 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, JobType.DownloadNewChaptersJob, 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, connector, recurring, recurrence, parentJobId)
|
||||||
{
|
{
|
||||||
this.manga = manga;
|
this.manga = manga;
|
||||||
this.translatedLanguage = translatedLanguage;
|
this.translatedLanguage = translatedLanguage;
|
||||||
@ -31,9 +31,8 @@ public class DownloadNewChapters : Job
|
|||||||
return $"{id} Manga: {manga}";
|
return $"{id} Manga: {manga}";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IEnumerable<Job> ExecuteReturnSubTasksInternal(JobBoss jobBoss)
|
protected override IEnumerable<Job> ExecuteReturnSubTasksInternal()
|
||||||
{
|
{
|
||||||
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();
|
||||||
@ -43,8 +42,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
@ -13,13 +13,9 @@ 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 }
|
|
||||||
|
|
||||||
public JobType jobType;
|
internal Job(GlobalBase clone, MangaConnector connector, bool recurring = false, TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone)
|
||||||
|
|
||||||
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;
|
||||||
@ -31,10 +27,9 @@ public abstract class Job : GlobalBase
|
|||||||
this.parentJobId = parentJobId;
|
this.parentJobId = parentJobId;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Job(GlobalBase clone, JobType jobType, MangaConnector connector, DateTime lastExecution, bool recurring = false,
|
internal Job(GlobalBase clone, 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;
|
||||||
@ -86,13 +81,13 @@ public abstract class Job : GlobalBase
|
|||||||
subJob.Cancel();
|
subJob.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Job> ExecuteReturnSubTasks(JobBoss jobBoss)
|
public IEnumerable<Job> ExecuteReturnSubTasks()
|
||||||
{
|
{
|
||||||
progressToken.Start();
|
progressToken.Start();
|
||||||
subJobs = ExecuteReturnSubTasksInternal(jobBoss);
|
subJobs = ExecuteReturnSubTasksInternal();
|
||||||
lastExecution = DateTime.Now;
|
lastExecution = DateTime.Now;
|
||||||
return subJobs;
|
return subJobs;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract IEnumerable<Job> ExecuteReturnSubTasksInternal(JobBoss jobBoss);
|
protected abstract IEnumerable<Job> ExecuteReturnSubTasksInternal();
|
||||||
}
|
}
|
@ -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(this).ToArray();
|
Job[] subJobs = jobQueue.Peek().ExecuteReturnSubTasks().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))
|
||||||
|
@ -23,20 +23,7 @@ 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()
|
||||||
@ -51,7 +38,9 @@ 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()
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
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>();
|
|
||||||
}
|
|
||||||
}
|
|
@ -41,33 +41,15 @@ public abstract class LibraryConnector : GlobalBase
|
|||||||
Method = HttpMethod.Get,
|
Method = HttpMethod.Get,
|
||||||
RequestUri = new Uri(url)
|
RequestUri = new Uri(url)
|
||||||
};
|
};
|
||||||
try
|
HttpResponseMessage response = client.Send(requestMessage);
|
||||||
{
|
logger?.WriteLine("LibraryManager.NetClient", $"GET {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}");
|
||||||
|
|
||||||
HttpResponseMessage response = client.Send(requestMessage);
|
if(response.StatusCode is HttpStatusCode.Unauthorized && response.RequestMessage!.RequestUri!.AbsoluteUri != url)
|
||||||
logger?.WriteLine("LibraryManager.NetClient",
|
return MakeRequest(response.RequestMessage!.RequestUri!.AbsoluteUri, authScheme, auth, logger);
|
||||||
$"GET {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}");
|
else if (response.IsSuccessStatusCode)
|
||||||
|
return response.Content.ReadAsStream();
|
||||||
if (response.StatusCode is HttpStatusCode.Unauthorized &&
|
else
|
||||||
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)
|
||||||
|
@ -11,32 +11,23 @@ namespace Tranga;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public struct Manga
|
public struct Manga
|
||||||
{
|
{
|
||||||
public string sortName { get; private set; }
|
public string sortName { get; }
|
||||||
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; private set; }
|
public string? description { get; }
|
||||||
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; }
|
public string? coverFileNameInCache { get; set; }
|
||||||
// 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; private set; }
|
public int? year { get; }
|
||||||
public string? originalLanguage { get; }
|
public string? originalLanguage { get; }
|
||||||
// ReSharper disable twice MemberCanBePrivate.Global
|
// ReSharper disable twice MemberCanBePrivate.Global
|
||||||
public string status { get; private set; }
|
public string status { get; }
|
||||||
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; }
|
||||||
@ -47,7 +38,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, ReleaseStatusByte releaseStatus = 0, string? websiteUrl = null, 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, string? folderName = null, float? ignoreChaptersBelow = 0)
|
||||||
{
|
{
|
||||||
this.sortName = sortName;
|
this.sortName = sortName;
|
||||||
this.authors = authors;
|
this.authors = authors;
|
||||||
@ -69,31 +60,6 @@ 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()
|
||||||
@ -126,11 +92,11 @@ public struct Manga
|
|||||||
latestChapterDownloaded = latestChapterDownloaded < chapterNumber ? chapterNumber : latestChapterDownloaded;
|
latestChapterDownloaded = latestChapterDownloaded < chapterNumber ? chapterNumber : latestChapterDownloaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveSeriesInfoJson(string downloadDirectory, bool overwrite = false)
|
public void SaveSeriesInfoJson(string downloadDirectory)
|
||||||
{
|
{
|
||||||
string publicationFolder = CreatePublicationFolder(downloadDirectory);
|
string publicationFolder = CreatePublicationFolder(downloadDirectory);
|
||||||
string seriesInfoPath = Path.Join(publicationFolder, "series.json");
|
string seriesInfoPath = Path.Join(publicationFolder, "series.json");
|
||||||
if(overwrite || (!overwrite && !File.Exists(seriesInfoPath)))
|
if(!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);
|
||||||
@ -139,7 +105,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));
|
SeriesInfo si = new (new Metadata(this.sortName, this.year.ToString() ?? string.Empty, this.status, this.description ?? ""));
|
||||||
return System.Text.Json.JsonSerializer.Serialize(si);
|
return System.Text.Json.JsonSerializer.Serialize(si);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,11 +150,6 @@ public struct Manga
|
|||||||
"cancellato",
|
"cancellato",
|
||||||
"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)
|
||||||
{
|
{
|
||||||
|
@ -36,11 +36,6 @@ 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 =
|
||||||
@ -107,18 +102,9 @@ 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, releaseStatus);
|
year, originalLanguage, status, publicationId);
|
||||||
cachedPublications.Add(manga);
|
cachedPublications.Add(manga);
|
||||||
return manga;
|
return manga;
|
||||||
}
|
}
|
||||||
|
@ -37,8 +37,6 @@ public abstract class MangaConnector : GlobalBase
|
|||||||
public abstract Manga[] GetManga(string publicationTitle = "");
|
public abstract Manga[] GetManga(string publicationTitle = "");
|
||||||
|
|
||||||
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.
|
||||||
@ -150,8 +148,7 @@ 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>
|
||||||
/// <param name="retries">Number of times to retry to copy the cover (or download it first)</param>
|
public void CopyCoverFromCacheToDownloadLocation(Manga manga)
|
||||||
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
|
||||||
@ -164,18 +161,6 @@ 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);
|
||||||
|
@ -72,10 +72,13 @@ public class MangaDex : MangaConnector
|
|||||||
return retManga.ToArray();
|
return retManga.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Manga? GetMangaFromId(string publicationId)
|
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}");
|
||||||
DownloadClient.RequestResult requestResult =
|
DownloadClient.RequestResult requestResult =
|
||||||
downloadClient.MakeRequest($"https://api.mangadex.org/manga/{publicationId}", (byte)RequestType.Manga);
|
downloadClient.MakeRequest($"https://api.mangadex.org/manga/{id}", (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);
|
||||||
@ -84,14 +87,6 @@ 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"))
|
||||||
@ -175,14 +170,6 @@ 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,
|
||||||
@ -196,8 +183,7 @@ public class MangaDex : MangaConnector
|
|||||||
year,
|
year,
|
||||||
originalLanguage,
|
originalLanguage,
|
||||||
status,
|
status,
|
||||||
publicationId,
|
publicationId
|
||||||
releaseStatus
|
|
||||||
);
|
);
|
||||||
cachedPublications.Add(pub);
|
cachedPublications.Add(pub);
|
||||||
return pub;
|
return pub;
|
||||||
|
@ -39,11 +39,6 @@ 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 =
|
||||||
@ -92,7 +87,6 @@ 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;
|
||||||
@ -116,11 +110,6 @@ 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();
|
||||||
@ -147,7 +136,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, releaseStatus);
|
year, originalLanguage, status, publicationId);
|
||||||
cachedPublications.Add(manga);
|
cachedPublications.Add(manga);
|
||||||
return manga;
|
return manga;
|
||||||
}
|
}
|
||||||
|
@ -32,11 +32,6 @@ 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\/(.*)(\/.*)*");
|
||||||
@ -77,7 +72,6 @@ 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", "");
|
||||||
@ -110,14 +104,6 @@ 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:']/..")
|
||||||
@ -125,7 +111,8 @@ 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, year, originalLanguage, status, publicationId, releaseStatus);
|
coverFileNameInCache, links,
|
||||||
|
year, originalLanguage, status, publicationId);
|
||||||
cachedPublications.Add(manga);
|
cachedPublications.Add(manga);
|
||||||
return manga;
|
return manga;
|
||||||
}
|
}
|
||||||
|
@ -54,11 +54,6 @@ 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 =
|
||||||
@ -79,7 +74,6 @@ 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"));
|
||||||
|
|
||||||
@ -105,11 +99,6 @@ 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(" - ");
|
||||||
@ -133,7 +122,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, releaseStatus);
|
year, originalLanguage, status, publicationId);
|
||||||
cachedPublications.Add(manga);
|
cachedPublications.Add(manga);
|
||||||
return manga;
|
return manga;
|
||||||
}
|
}
|
||||||
@ -167,18 +156,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(@"https:\/\/chapmanganato.com/manga-[A-z0-9]+\/chapter-([0-9\.]+)");
|
Regex chapterRex = new(@"Chapter ([0-9]+(\.[0-9]+)*){1}.*");
|
||||||
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();
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
using System.Data;
|
using System.Net;
|
||||||
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;
|
||||||
@ -18,102 +16,21 @@ 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 requestUrl = "https://mangasee123.com/_search.php";
|
string sanitizedTitle = WebUtility.UrlEncode(publicationTitle);
|
||||||
|
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>();
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
if (requestResult.htmlDocument is null)
|
||||||
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)
|
||||||
@ -127,12 +44,35 @@ 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", "");
|
||||||
@ -165,14 +105,6 @@ 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:']/..")
|
||||||
@ -181,7 +113,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, releaseStatus);
|
year, originalLanguage, status, publicationId);
|
||||||
cachedPublications.Add(manga);
|
cachedPublications.Add(manga);
|
||||||
return manga;
|
return manga;
|
||||||
}
|
}
|
||||||
@ -210,7 +142,7 @@ public class Mangasee : MangaConnector
|
|||||||
}
|
}
|
||||||
catch (HttpRequestException e)
|
catch (HttpRequestException e)
|
||||||
{
|
{
|
||||||
Log($"Failed to load https://mangasee123.com/rss/{manga.publicationId}.xml \n\r{e}");
|
Log($"Failed to load XML\n\r{e}");
|
||||||
return Array.Empty<Chapter>();
|
return Array.Empty<Chapter>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,11 +54,6 @@ 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 =
|
||||||
@ -69,9 +64,7 @@ public class Mangaworld: MangaConnector
|
|||||||
if (requestResult.htmlDocument is null)
|
if (requestResult.htmlDocument is null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
Regex idRex = new (@"https:\/\/www\.mangaworld\.bz\/manga\/([0-9]+\/[0-9A-z\-]+)");
|
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, url.Split('/')[^2]);
|
||||||
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)
|
||||||
@ -79,7 +72,6 @@ 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"));
|
||||||
|
|
||||||
@ -102,15 +94,6 @@ 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", "");
|
||||||
|
|
||||||
@ -122,7 +105,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, releaseStatus);
|
year, originalLanguage, status, publicationId);
|
||||||
cachedPublications.Add(manga);
|
cachedPublications.Add(manga);
|
||||||
return manga;
|
return manga;
|
||||||
}
|
}
|
||||||
|
@ -304,34 +304,6 @@ 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))
|
||||||
|
Loading…
Reference in New Issue
Block a user