Added manga data and pipeline.

This commit is contained in:
2025-05-23 02:40:06 -04:00
parent ec1713c95f
commit f760cff21f
27 changed files with 490 additions and 326 deletions

View File

@@ -0,0 +1,11 @@
namespace MangaReader.Core.Data;
public class ChapterPage
{
public int ChapterPageId { get; set; }
public int MangaChapterId { get; set; }
public required MangaChapter MangaChapter { get; set; }
public int PageNumber { get; set; }
}

View File

@@ -0,0 +1,12 @@
namespace MangaReader.Core.Data;
public class ChapterSource
{
public int MangaChapterId { get; set; }
public required MangaChapter Chapter { get; set; }
public int SourceId { get; set; }
public required Source Source { get; set; }
public required string Url { get; set; }
}

View File

@@ -0,0 +1,7 @@
namespace MangaReader.Core.Data;
public class Genre
{
public int GenreId { get; set; }
public required string Name { get; set; }
}

View File

@@ -1,6 +1,4 @@
using Microsoft.EntityFrameworkCore;
namespace MangaReader.Core.Data;
namespace MangaReader.Core.Data;
public class Manga
{
@@ -23,298 +21,4 @@ public class Manga
Genres = new HashSet<MangaGenre>();
Chapters = new HashSet<MangaChapter>();
}
}
public class MangaCover
{
public int MangaCoverId { get; set; }
public int MangaId { get; set; }
public required Manga Manga { get; set; }
public required Guid Guid { get; set; }
public required string FileExtension { get; set; }
public string? Description { get; set; }
public bool IsPrimary { get; set; }
}
public class MangaTitle
{
public int MangaTitleId { get; set; }
public int MangaId { get; set; }
public required Manga Manga { get; set; }
public required string TitleEntry { get; set; }
public TitleType TitleType { get; set; }
}
public enum TitleType
{
Primary,
OfficialTranslation,
FanTranslation,
Synonym,
Abbreviation,
Romaji,
Japanese
}
public class Source
{
public int SourceId { get; set; }
public required string Name { get; set; }
}
public class MangaSource
{
public int MangaId { get; set; }
public required Manga Manga { get; set; }
public int SourceId { get; set; }
public required Source Source { get; set; }
public required string Url { get; set; }
}
public class Genre
{
public int GenreId { get; set; }
public required string Name { get; set; }
}
public class MangaGenre
{
public int MangaId { get; set; }
public required Manga Manga { get; set; }
public int GenreId { get; set; }
public required Genre Genre { get; set; }
}
public class MangaChapter
{
public int MangaChapterId { get; set; }
public int MangaId { get; set; }
public required Manga Manga { get; set; }
public int? VolumeNumber { get; set; }
public int ChapterNumber { get; set; }
public string? Title { get; set; }
public virtual ICollection<ChapterSource> Sources { get; set; } = [];
public virtual ICollection<ChapterPage> Pages { get; set; } = [];
}
public class ChapterSource
{
public int MangaChapterId { get; set; }
public required MangaChapter Chapter { get; set; }
public int SourceId { get; set; }
public required Source Source { get; set; }
public required string Url { get; set; }
}
public class ChapterPage
{
public int ChapterPageId { get; set; }
public int MangaChapterId { get; set; }
public required MangaChapter MangaChapter { get; set; }
public int PageNumber { get; set; }
}
public class MangaContext(DbContextOptions options) : DbContext(options)
{
public DbSet<Manga> Mangas { get; set; }
public DbSet<MangaCover> MangaCovers { get; set; }
public DbSet<MangaTitle> MangaTitles { get; set; }
public DbSet<Source> Sources { get; set; }
public DbSet<MangaSource> MangaSources { get; set; }
public DbSet<Genre> Genres { get; set; }
public DbSet<MangaGenre> MangaGenres { get; set; }
public DbSet<MangaChapter> MangaChapters { get; set; }
public DbSet<ChapterSource> ChapterSources { get; set; }
public DbSet<ChapterPage> ChapterPages { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
ConfigureManga(modelBuilder);
ConfigureMangaCover(modelBuilder);
ConfigureMangaTitle(modelBuilder);
ConfigureSource(modelBuilder);
ConfigureMangaSource(modelBuilder);
ConfigureGenre(modelBuilder);
ConfigureMangaGenre(modelBuilder);
ConfigureMangaChapter(modelBuilder);
ConfigureChapterSource(modelBuilder);
ConfigureChapterPage(modelBuilder);
}
private static void ConfigureManga(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Manga>()
.HasKey(x => x.MangaId);
modelBuilder.Entity<Manga>()
.HasIndex(x => x.Slug)
.IsUnique();
}
private static void ConfigureMangaCover(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<MangaCover>()
.HasKey(x => x.MangaCoverId);
modelBuilder
.Entity<MangaCover>()
.HasOne(x => x.Manga)
.WithMany(x => x.Covers)
.HasForeignKey(x => x.MangaId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MangaCover>()
.HasIndex(x => x.Guid)
.IsUnique();
//modelBuilder
// .Entity<MangaCover>()
// .HasIndex(x => new { x.MangaId, x.IsPrimary })
// .IsUnique()
// .HasFilter("[IsPrimary] = 1"); // Enforce only one primary cover per manga
}
private static void ConfigureMangaTitle(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<MangaTitle>()
.HasKey(mangaTitle => mangaTitle.MangaTitleId);
modelBuilder.Entity<MangaTitle>()
.HasIndex(mangaTitle => new { mangaTitle.MangaId, mangaTitle.TitleEntry })
.IsUnique();
modelBuilder
.Entity<MangaTitle>()
.HasIndex(mangaTitle => mangaTitle.TitleEntry);
modelBuilder
.Entity<MangaTitle>()
.HasOne(x => x.Manga)
.WithMany(x => x.Titles)
.HasForeignKey(x => x.MangaId)
.OnDelete(DeleteBehavior.Cascade);
}
private static void ConfigureSource(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Source>()
.HasKey(x => x.SourceId);
modelBuilder
.Entity<Source>()
.HasIndex(x => x.Name)
.IsUnique(true);
}
private static void ConfigureMangaSource(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<MangaSource>()
.HasKey(mangaSource => new { mangaSource.MangaId, mangaSource.SourceId });
modelBuilder.Entity<MangaSource>()
.HasIndex(x => x.Url)
.IsUnique();
modelBuilder
.Entity<MangaSource>()
.HasOne(x => x.Manga)
.WithMany(x => x.Sources)
.HasForeignKey(x => x.MangaId)
.OnDelete(DeleteBehavior.Cascade);
}
private static void ConfigureGenre(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Genre>()
.HasKey(x => x.GenreId);
modelBuilder
.Entity<Genre>()
.HasIndex(x => x.Name)
.IsUnique(true);
}
private static void ConfigureMangaGenre(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<MangaGenre>()
.HasKey(mangaGenre => new { mangaGenre.MangaId, mangaGenre.GenreId });
modelBuilder
.Entity<MangaGenre>()
.HasOne(x => x.Manga)
.WithMany(x => x.Genres)
.HasForeignKey(x => x.MangaId)
.OnDelete(DeleteBehavior.Cascade);
}
private static void ConfigureMangaChapter(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<MangaChapter>()
.HasKey(x => x.MangaChapterId);
modelBuilder
.Entity<MangaChapter>()
.HasOne(x => x.Manga)
.WithMany(x => x.Chapters)
.HasForeignKey(x => x.MangaId)
.OnDelete(DeleteBehavior.Cascade);
}
private static void ConfigureChapterSource(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<ChapterSource>()
.HasKey(chapterSource => new { chapterSource.MangaChapterId, chapterSource.SourceId });
modelBuilder
.Entity<ChapterSource>()
.HasOne(x => x.Chapter)
.WithMany(x => x.Sources)
.HasForeignKey(x => x.MangaChapterId)
.OnDelete(DeleteBehavior.Cascade);
}
private static void ConfigureChapterPage(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<ChapterPage>()
.HasKey(chapterPage => chapterPage.ChapterPageId);
modelBuilder
.Entity<ChapterPage>()
.HasIndex(chapterPage => new { chapterPage.MangaChapterId, chapterPage.PageNumber })
.IsUnique(true);
modelBuilder
.Entity<ChapterPage>()
.HasOne(x => x.MangaChapter)
.WithMany(x => x.Pages)
.HasForeignKey(x => x.MangaChapterId)
.OnDelete(DeleteBehavior.Cascade);
}
}

View File

@@ -0,0 +1,16 @@
namespace MangaReader.Core.Data;
public class MangaChapter
{
public int MangaChapterId { get; set; }
public int MangaId { get; set; }
public required Manga Manga { get; set; }
public int? VolumeNumber { get; set; }
public required float ChapterNumber { get; set; }
public string? Title { get; set; }
public virtual ICollection<ChapterSource> Sources { get; set; } = [];
public virtual ICollection<ChapterPage> Pages { get; set; } = [];
}

View File

@@ -0,0 +1,193 @@
using Microsoft.EntityFrameworkCore;
namespace MangaReader.Core.Data;
public class MangaContext(DbContextOptions options) : DbContext(options)
{
public DbSet<Manga> Mangas { get; set; }
public DbSet<MangaCover> MangaCovers { get; set; }
public DbSet<MangaTitle> MangaTitles { get; set; }
public DbSet<Source> Sources { get; set; }
public DbSet<MangaSource> MangaSources { get; set; }
public DbSet<Genre> Genres { get; set; }
public DbSet<MangaGenre> MangaGenres { get; set; }
public DbSet<MangaChapter> MangaChapters { get; set; }
public DbSet<ChapterSource> ChapterSources { get; set; }
public DbSet<ChapterPage> ChapterPages { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
ConfigureManga(modelBuilder);
ConfigureMangaCover(modelBuilder);
ConfigureMangaTitle(modelBuilder);
ConfigureSource(modelBuilder);
ConfigureMangaSource(modelBuilder);
ConfigureGenre(modelBuilder);
ConfigureMangaGenre(modelBuilder);
ConfigureMangaChapter(modelBuilder);
ConfigureChapterSource(modelBuilder);
ConfigureChapterPage(modelBuilder);
}
private static void ConfigureManga(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Manga>()
.HasKey(x => x.MangaId);
modelBuilder.Entity<Manga>()
.HasIndex(x => x.Slug)
.IsUnique();
}
private static void ConfigureMangaCover(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<MangaCover>()
.HasKey(x => x.MangaCoverId);
modelBuilder
.Entity<MangaCover>()
.HasOne(x => x.Manga)
.WithMany(x => x.Covers)
.HasForeignKey(x => x.MangaId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MangaCover>()
.HasIndex(x => x.Guid)
.IsUnique();
//modelBuilder
// .Entity<MangaCover>()
// .HasIndex(x => new { x.MangaId, x.IsPrimary })
// .IsUnique()
// .HasFilter("[IsPrimary] = 1"); // Enforce only one primary cover per manga
}
private static void ConfigureMangaTitle(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<MangaTitle>()
.HasKey(mangaTitle => mangaTitle.MangaTitleId);
modelBuilder.Entity<MangaTitle>()
.HasIndex(mangaTitle => new { mangaTitle.MangaId, mangaTitle.TitleEntry })
.IsUnique();
modelBuilder
.Entity<MangaTitle>()
.HasIndex(mangaTitle => mangaTitle.TitleEntry);
modelBuilder
.Entity<MangaTitle>()
.HasOne(x => x.Manga)
.WithMany(x => x.Titles)
.HasForeignKey(x => x.MangaId)
.OnDelete(DeleteBehavior.Cascade);
}
private static void ConfigureSource(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Source>()
.HasKey(x => x.SourceId);
modelBuilder
.Entity<Source>()
.HasIndex(x => x.Name)
.IsUnique(true);
}
private static void ConfigureMangaSource(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<MangaSource>()
.HasKey(mangaSource => new { mangaSource.MangaId, mangaSource.SourceId });
modelBuilder.Entity<MangaSource>()
.HasIndex(x => x.Url)
.IsUnique();
modelBuilder
.Entity<MangaSource>()
.HasOne(x => x.Manga)
.WithMany(x => x.Sources)
.HasForeignKey(x => x.MangaId)
.OnDelete(DeleteBehavior.Cascade);
}
private static void ConfigureGenre(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Genre>()
.HasKey(x => x.GenreId);
modelBuilder
.Entity<Genre>()
.HasIndex(x => x.Name)
.IsUnique(true);
}
private static void ConfigureMangaGenre(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<MangaGenre>()
.HasKey(mangaGenre => new { mangaGenre.MangaId, mangaGenre.GenreId });
modelBuilder
.Entity<MangaGenre>()
.HasOne(x => x.Manga)
.WithMany(x => x.Genres)
.HasForeignKey(x => x.MangaId)
.OnDelete(DeleteBehavior.Cascade);
}
private static void ConfigureMangaChapter(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<MangaChapter>()
.HasKey(x => x.MangaChapterId);
modelBuilder
.Entity<MangaChapter>()
.HasOne(x => x.Manga)
.WithMany(x => x.Chapters)
.HasForeignKey(x => x.MangaId)
.OnDelete(DeleteBehavior.Cascade);
}
private static void ConfigureChapterSource(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<ChapterSource>()
.HasKey(chapterSource => new { chapterSource.MangaChapterId, chapterSource.SourceId });
modelBuilder
.Entity<ChapterSource>()
.HasOne(x => x.Chapter)
.WithMany(x => x.Sources)
.HasForeignKey(x => x.MangaChapterId)
.OnDelete(DeleteBehavior.Cascade);
}
private static void ConfigureChapterPage(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<ChapterPage>()
.HasKey(chapterPage => chapterPage.ChapterPageId);
modelBuilder
.Entity<ChapterPage>()
.HasIndex(chapterPage => new { chapterPage.MangaChapterId, chapterPage.PageNumber })
.IsUnique(true);
modelBuilder
.Entity<ChapterPage>()
.HasOne(x => x.MangaChapter)
.WithMany(x => x.Pages)
.HasForeignKey(x => x.MangaChapterId)
.OnDelete(DeleteBehavior.Cascade);
}
}

View File

@@ -0,0 +1,14 @@
namespace MangaReader.Core.Data;
public class MangaCover
{
public int MangaCoverId { get; set; }
public int MangaId { get; set; }
public required Manga Manga { get; set; }
public required Guid Guid { get; set; }
public required string FileExtension { get; set; }
public string? Description { get; set; }
public bool IsPrimary { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace MangaReader.Core.Data;
public class MangaGenre
{
public int MangaId { get; set; }
public required Manga Manga { get; set; }
public int GenreId { get; set; }
public required Genre Genre { get; set; }
}

View File

@@ -0,0 +1,12 @@
namespace MangaReader.Core.Data;
public class MangaSource
{
public int MangaId { get; set; }
public required Manga Manga { get; set; }
public int SourceId { get; set; }
public required Source Source { get; set; }
public required string Url { get; set; }
}

View File

@@ -0,0 +1,12 @@
namespace MangaReader.Core.Data;
public class MangaTitle
{
public int MangaTitleId { get; set; }
public int MangaId { get; set; }
public required Manga Manga { get; set; }
public required string TitleEntry { get; set; }
public TitleType TitleType { get; set; }
}

View File

@@ -0,0 +1,7 @@
namespace MangaReader.Core.Data;
public class Source
{
public int SourceId { get; set; }
public required string Name { get; set; }
}

View File

@@ -0,0 +1,12 @@
namespace MangaReader.Core.Data;
public enum TitleType
{
Primary,
OfficialTranslation,
FanTranslation,
Synonym,
Abbreviation,
Romaji,
Japanese
}

View File

@@ -12,7 +12,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="WebSearch\MangaDex\" />
<Folder Include="Search\MangaDex\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
using MangaReader.Core.WebCrawlers;
namespace MangaReader.Core.Pipeline;
public interface IMangaPipeline
{
Task RunAsync(SourceManga mangaDto);
}

View File

@@ -0,0 +1,144 @@
using MangaReader.Core.Data;
using MangaReader.Core.WebCrawlers;
using Microsoft.EntityFrameworkCore;
using System.Text.RegularExpressions;
namespace MangaReader.Core.Pipeline;
public partial class MangaPipeline(MangaContext context) : IMangaPipeline
{
public async Task RunAsync(SourceManga sourceManga)
{
Manga manga = await GetOrAddMangaAsync(sourceManga);
foreach (string alternateTitle in sourceManga.AlternateTitles)
{
await AddTitleAsync(manga, alternateTitle);
}
foreach (string genre in sourceManga.Genres)
{
await LinkGenreAsync(manga, genre);
}
foreach (SourceMangaChapter chapter in sourceManga.Chapters)
{
await AddChapterAsync(manga, chapter);
}
context.SaveChanges();
}
private async Task<Manga> GetOrAddMangaAsync(SourceManga sourceManga)
{
Manga? manga = await context.Mangas.FirstOrDefaultAsync(manga => manga.Title == sourceManga.Title);
if (manga != null)
return manga;
manga = new()
{
Title = sourceManga.Title,
Slug = GenerateSlug(sourceManga.Title),
};
context.Add(manga);
return manga;
}
private static string GenerateSlug(string title)
{
title = title.ToLowerInvariant();
title = RemoveInvalidCharsRegex().Replace(title, ""); // remove invalid chars
title = RemoveSpacesWithDashRegex().Replace(title, "-"); // replace spaces with dash
return title.Trim('-');
}
[GeneratedRegex(@"[^a-z0-9\s-]")]
private static partial Regex RemoveInvalidCharsRegex();
[GeneratedRegex(@"\s+")]
private static partial Regex RemoveSpacesWithDashRegex();
private async Task AddTitleAsync(Manga manga, string title)
{
MangaTitle? mangaTitle = await context.MangaTitles.FirstOrDefaultAsync(mt => mt.Manga == manga && mt.TitleEntry == title);
if (mangaTitle != null)
return;
mangaTitle = new()
{
Manga = manga,
TitleEntry = title,
};
context.MangaTitles.Add(mangaTitle);
}
private async Task LinkGenreAsync(Manga manga, string genreName)
{
Genre genre = await GetOrAddGenreAsync(genreName);
MangaGenre? mangaGenre = await context.MangaGenres.FirstOrDefaultAsync(x => x.Manga == manga && x.Genre == genre);
if (mangaGenre != null)
return;
mangaGenre = new()
{
Manga = manga,
Genre = genre
};
context.MangaGenres.Add(mangaGenre);
}
private async Task<Genre> GetOrAddGenreAsync(string genreName)
{
Genre? genre = await context.Genres.FirstOrDefaultAsync(x => x.Name == genreName);
if (genre != null)
return genre;
genre = new()
{
Name = genreName,
};
await context.Genres.AddAsync(genre);
return genre;
}
private async Task AddChapterAsync(Manga manga, SourceMangaChapter sourceeMangaChapter)
{
MangaChapter mangaChapter = await context.MangaChapters.FirstOrDefaultAsync(x => x.ChapterNumber == sourceeMangaChapter.Number)
?? AddMangaChapter(manga, sourceeMangaChapter);
if (mangaChapter.VolumeNumber is null && sourceeMangaChapter.Volume is not null)
{
mangaChapter.VolumeNumber = sourceeMangaChapter.Volume;
}
if (mangaChapter.Title is null && sourceeMangaChapter.Name is not null)
{
mangaChapter.Title = sourceeMangaChapter.Name;
}
}
private MangaChapter AddMangaChapter(Manga manga, SourceMangaChapter sourceeMangaChapter)
{
MangaChapter mangaChapter = new()
{
Manga = manga,
ChapterNumber = sourceeMangaChapter.Number
};
context.MangaChapters.Add(mangaChapter);
return mangaChapter;
}
}

View File

@@ -1,6 +1,6 @@
namespace MangaReader.Core.WebSearch;
namespace MangaReader.Core.Search;
public interface IMangaWebSearch<T>
public interface IMangaSearchProvider<T>
{
Task<MangaSearchResult[]> SearchAsync(string keyword);
}

View File

@@ -1,9 +1,9 @@
using MangaReader.Core.HttpService;
using System.Text.Json;
namespace MangaReader.Core.WebSearch;
namespace MangaReader.Core.Search;
public abstract class MangaWebSearchBase<T>(IHttpService httpService) : IMangaWebSearch<T>
public abstract class MangaSearchProviderBase<T>(IHttpService httpService) : IMangaSearchProvider<T>
{
private static JsonSerializerOptions _jsonSerializerOptions = new()
{

View File

@@ -1,7 +1,8 @@
namespace MangaReader.Core.WebSearch;
namespace MangaReader.Core.Search;
public record MangaSearchResult
{
public required string Source { get; init; }
public required string Url { get; init; }
public required string Title { get; init; }
public string? Author { get; init; }

View File

@@ -1,8 +1,8 @@
using MangaReader.Core.HttpService;
namespace MangaReader.Core.WebSearch.NatoManga;
namespace MangaReader.Core.Search.NatoManga;
public class NatoMangaWebSearch(IHttpService httpService) : MangaWebSearchBase<NatoMangaSearchResult[]>(httpService)
public class NatoMangaSearchProvider(IHttpService httpService) : MangaSearchProviderBase<NatoMangaSearchResult[]>(httpService)
{
// https://www.natomanga.com/home/search/json?searchword=gal_can_t_be_kind
@@ -16,6 +16,7 @@ public class NatoMangaWebSearch(IHttpService httpService) : MangaWebSearchBase<N
IEnumerable<MangaSearchResult> mangaSearchResults = searchResult.Select(searchResult =>
new MangaSearchResult()
{
Source = "NatoManga",
Title = searchResult.Name,
Url = searchResult.Url
});

View File

@@ -1,4 +1,4 @@
namespace MangaReader.Core.WebSearch.NatoManga;
namespace MangaReader.Core.Search.NatoManga;
public record NatoMangaSearchResult
{

View File

@@ -2,5 +2,5 @@
public interface IMangaWebCrawler
{
MangaDTO GetManga(string url);
SourceManga GetManga(string url);
}

View File

@@ -6,12 +6,12 @@ namespace MangaReader.Core.WebCrawlers.MangaNato;
public class MangaNatoWebCrawler : MangaWebCrawler
{
public override MangaDTO GetManga(string url)
public override SourceManga GetManga(string url)
{
HtmlDocument document = GetHtmlDocument(url);
MangaNatoMangaDocument node = new(document);
MangaDTO manga = new()
SourceManga manga = new()
{
Title = node.TitleNode.InnerText,
AlternateTitles = GetAlternateTitles(node.AlternateTitlesNode),
@@ -101,9 +101,9 @@ public class MangaNatoWebCrawler : MangaWebCrawler
return (int)Math.Round(average / best * 100);
}
private static List<MangaChapterDTO> GetChapters(HtmlNodeCollection chapterNodes)
private static List<SourceMangaChapter> GetChapters(HtmlNodeCollection chapterNodes)
{
List<MangaChapterDTO> chapters = [];
List<SourceMangaChapter> chapters = [];
foreach (var node in chapterNodes)
{
@@ -111,7 +111,7 @@ public class MangaNatoWebCrawler : MangaWebCrawler
HtmlNode chapterViewNode = node.SelectSingleNode(".//span[contains(@class, 'chapter-view')]");
HtmlNode chapterTimeNode = node.SelectSingleNode(".//span[contains(@class, 'chapter-time')]");
MangaChapterDTO chapter = new()
SourceMangaChapter chapter = new()
{
Number = GetChapterNumber(chapterNameNode),
Name = chapterNameNode.InnerText,

View File

@@ -4,7 +4,7 @@ namespace MangaReader.Core.WebCrawlers;
public abstract class MangaWebCrawler : IMangaWebCrawler
{
public abstract MangaDTO GetManga(string url);
public abstract SourceManga GetManga(string url);
protected virtual HtmlDocument GetHtmlDocument(string url)
{

View File

@@ -6,12 +6,12 @@ namespace MangaReader.Core.WebCrawlers.NatoManga;
public class NatoMangaWebCrawler : MangaWebCrawler
{
public override MangaDTO GetManga(string url)
public override SourceManga GetManga(string url)
{
HtmlDocument document = GetHtmlDocument(url);
NatoMangaHtmlDocument node = new(document);
MangaDTO manga = new()
SourceManga manga = new()
{
Title = node.TitleNode?.InnerText ?? string.Empty,
//AlternateTitles = GetAlternateTitles(node.AlternateTitlesNode),
@@ -116,9 +116,9 @@ public class NatoMangaWebCrawler : MangaWebCrawler
return (int)Math.Round(average / best * 100);
}
private static List<MangaChapterDTO> GetChapters(HtmlNodeCollection? chapterNodes)
private static List<SourceMangaChapter> GetChapters(HtmlNodeCollection? chapterNodes)
{
List<MangaChapterDTO> chapters = [];
List<SourceMangaChapter> chapters = [];
if (chapterNodes == null)
return chapters;
@@ -137,7 +137,7 @@ public class NatoMangaWebCrawler : MangaWebCrawler
if (chapterNameNode == null)
continue;
MangaChapterDTO chapter = new()
SourceMangaChapter chapter = new()
{
Number = GetChapterNumber(chapterNameNode),
Name = chapterNameNode.InnerText,

View File

@@ -1,6 +1,6 @@
namespace MangaReader.Core.WebCrawlers;
public class MangaDTO
public class SourceManga
{
public required string Title { get; set; }
public string? Description { get; set; }
@@ -12,5 +12,5 @@ public class MangaDTO
public long? Views { get; set; }
public float? RatingPercent { get; set; }
public int? Votes { get; set; }
public List<MangaChapterDTO> Chapters { get; set; } = [];
public List<SourceMangaChapter> Chapters { get; set; } = [];
}

View File

@@ -1,9 +1,9 @@
namespace MangaReader.Core.WebCrawlers;
public class MangaChapterDTO
public class SourceMangaChapter
{
public int? Volume { get; set; }
public float? Number { get; set; }
public required float Number { get; set; }
public string? Name { get; set; }
public required string Url { get; set; }
public long? Views { get; set; }