diff --git a/MangaReader.Core/Data/Manga.cs b/MangaReader.Core/Data/Manga.cs index 147c9c2..e682811 100644 --- a/MangaReader.Core/Data/Manga.cs +++ b/MangaReader.Core/Data/Manga.cs @@ -4,11 +4,10 @@ public class Manga { public int MangaId { get; set; } public required string Slug { get; set; } - public required string Title { get; set; } - public string? Description { get; set; } public virtual ICollection Covers { get; set; } = []; public virtual ICollection Titles { get; set; } = []; + public virtual ICollection Descriptions { get; set; } = []; public virtual ICollection Sources { get; set; } = []; public virtual ICollection Contributors { get; set; } = []; public virtual ICollection Genres { get; set; } = []; diff --git a/MangaReader.Core/Data/MangaContext.cs b/MangaReader.Core/Data/MangaContext.cs index 33a27f8..2dde88d 100644 --- a/MangaReader.Core/Data/MangaContext.cs +++ b/MangaReader.Core/Data/MangaContext.cs @@ -7,6 +7,7 @@ public class MangaContext(DbContextOptions options) : DbContext(options) public DbSet Mangas { get; set; } public DbSet MangaCovers { get; set; } public DbSet MangaTitles { get; set; } + public DbSet MangaDescriptions { get; set; } public DbSet Sources { get; set; } public DbSet MangaSources { get; set; } public DbSet Contributors { get; set; } @@ -24,6 +25,7 @@ public class MangaContext(DbContextOptions options) : DbContext(options) ConfigureManga(modelBuilder); ConfigureMangaCover(modelBuilder); ConfigureMangaTitle(modelBuilder); + ConfigureMangaDescription(modelBuilder); ConfigureSource(modelBuilder); ConfigureMangaSource(modelBuilder); ConfigureContributor(modelBuilder); @@ -41,9 +43,9 @@ public class MangaContext(DbContextOptions options) : DbContext(options) .Entity() .HasKey(x => x.MangaId); - modelBuilder.Entity() - .HasIndex(x => x.Title) - .IsUnique(); + //modelBuilder.Entity() + // .HasIndex(x => x.Title) + // .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) @@ -81,7 +83,15 @@ public class MangaContext(DbContextOptions options) : DbContext(options) .HasKey(mangaTitle => mangaTitle.MangaTitleId); modelBuilder.Entity() - .HasIndex(mangaTitle => new { mangaTitle.MangaId, mangaTitle.Name }) + .Property(mt => mt.Name) + .IsRequired(); + + modelBuilder.Entity() + .Property(mt => mt.Language) + .IsRequired(); + + modelBuilder.Entity() + .HasIndex(mangaTitle => new { mangaTitle.MangaId, mangaTitle.Name, mangaTitle.Language }) .IsUnique(); modelBuilder @@ -96,6 +106,36 @@ public class MangaContext(DbContextOptions options) : DbContext(options) .OnDelete(DeleteBehavior.Cascade); } + private static void ConfigureMangaDescription(ModelBuilder modelBuilder) + { + modelBuilder + .Entity() + .HasKey(mangaTitle => mangaTitle.MangaTitleId); + + modelBuilder.Entity() + .Property(mt => mt.Name) + .IsRequired(); + + modelBuilder.Entity() + .Property(mt => mt.Language) + .IsRequired(); + + modelBuilder.Entity() + .HasIndex(mangaTitle => new { mangaTitle.MangaId, mangaTitle.Name, mangaTitle.Language }) + .IsUnique(); + + modelBuilder + .Entity() + .HasIndex(mangaTitle => mangaTitle.Name); + + modelBuilder + .Entity() + .HasOne(x => x.Manga) + .WithMany(x => x.Descriptions) + .HasForeignKey(x => x.MangaId) + .OnDelete(DeleteBehavior.Cascade); + } + private static void ConfigureSource(ModelBuilder modelBuilder) { modelBuilder diff --git a/MangaReader.Core/Data/MangaTitle.cs b/MangaReader.Core/Data/MangaTitle.cs index b931b06..4be82cc 100644 --- a/MangaReader.Core/Data/MangaTitle.cs +++ b/MangaReader.Core/Data/MangaTitle.cs @@ -1,4 +1,6 @@ -namespace MangaReader.Core.Data; +using MangaReader.Core.Common; + +namespace MangaReader.Core.Data; public class MangaTitle { @@ -8,5 +10,6 @@ public class MangaTitle public required Manga Manga { get; set; } public required string Name { get; set; } - public TitleType TitleType { get; set; } + public required Language Language { get; set; } + public bool IsPrimary { get; set; } } \ No newline at end of file diff --git a/MangaReader.Core/Data/TitleType.cs b/MangaReader.Core/Data/TitleType.cs index 3f13274..9b669c2 100644 --- a/MangaReader.Core/Data/TitleType.cs +++ b/MangaReader.Core/Data/TitleType.cs @@ -1,12 +1,12 @@ namespace MangaReader.Core.Data; -public enum TitleType -{ - Primary, - OfficialTranslation, - FanTranslation, - Synonym, - Abbreviation, - Romaji, - Japanese -} \ No newline at end of file +//public enum TitleType +//{ +// Primary, +// OfficialTranslation, +// FanTranslation, +// Synonym, +// Abbreviation, +// Romaji, +// Japanese +//} \ No newline at end of file diff --git a/MangaReader.Core/Extensions/ServiceCollectionExtensions.cs b/MangaReader.Core/Extensions/ServiceCollectionExtensions.cs index 6ea4405..4a10bb1 100644 --- a/MangaReader.Core/Extensions/ServiceCollectionExtensions.cs +++ b/MangaReader.Core/Extensions/ServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -using MangaReader.Core.HttpService; +using MangaReader.Core.Http; using MangaReader.Core.Metadata; using MangaReader.Core.Search; using MangaReader.Core.Sources.MangaDex.Api; diff --git a/MangaReader.Core/Http/HtmlLoader.cs b/MangaReader.Core/Http/HtmlLoader.cs new file mode 100644 index 0000000..bd5222c --- /dev/null +++ b/MangaReader.Core/Http/HtmlLoader.cs @@ -0,0 +1,16 @@ +using HtmlAgilityPack; + +namespace MangaReader.Core.Http; + +public class HtmlLoader(IHttpService httpService) : IHtmlLoader +{ + public async Task GetHtmlDocumentAsync(string url, CancellationToken cancellationToken) + { + string html = await httpService.GetStringAsync(url, cancellationToken); + + HtmlDocument doc = new(); + doc.LoadHtml(html); + + return doc; + } +} \ No newline at end of file diff --git a/MangaReader.Core/HttpService/HttpService.cs b/MangaReader.Core/Http/HttpService.cs similarity index 96% rename from MangaReader.Core/HttpService/HttpService.cs rename to MangaReader.Core/Http/HttpService.cs index 192c18d..da14ef8 100644 --- a/MangaReader.Core/HttpService/HttpService.cs +++ b/MangaReader.Core/Http/HttpService.cs @@ -1,4 +1,4 @@ -namespace MangaReader.Core.HttpService; +namespace MangaReader.Core.Http; public class HttpService(HttpClient httpClient) : IHttpService { diff --git a/MangaReader.Core/Http/IHtmlLoader.cs b/MangaReader.Core/Http/IHtmlLoader.cs new file mode 100644 index 0000000..37159eb --- /dev/null +++ b/MangaReader.Core/Http/IHtmlLoader.cs @@ -0,0 +1,8 @@ +using HtmlAgilityPack; + +namespace MangaReader.Core.Http; + +public interface IHtmlLoader +{ + Task GetHtmlDocumentAsync(string url, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/MangaReader.Core/HttpService/IHttpService.cs b/MangaReader.Core/Http/IHttpService.cs similarity index 84% rename from MangaReader.Core/HttpService/IHttpService.cs rename to MangaReader.Core/Http/IHttpService.cs index 3ded7b9..76ac4c3 100644 --- a/MangaReader.Core/HttpService/IHttpService.cs +++ b/MangaReader.Core/Http/IHttpService.cs @@ -1,4 +1,4 @@ -namespace MangaReader.Core.HttpService; +namespace MangaReader.Core.Http; public interface IHttpService { diff --git a/MangaReader.Core/Metadata/MangaWebCrawler.cs b/MangaReader.Core/Metadata/MangaWebCrawler.cs index a2e1dd3..6769d00 100644 --- a/MangaReader.Core/Metadata/MangaWebCrawler.cs +++ b/MangaReader.Core/Metadata/MangaWebCrawler.cs @@ -1,21 +1,17 @@ -using HtmlAgilityPack; - -namespace MangaReader.Core.Metadata; +namespace MangaReader.Core.Metadata; public abstract class MangaWebCrawler : IMangaMetadataProvider { public abstract string SourceId { get; } public abstract Task GetMangaAsync(string url, CancellationToken cancellationToken); - protected virtual async Task GetHtmlDocumentAsync(string url, CancellationToken cancellationToken) - { - HtmlWeb web = new() - { - UsingCacheIfExists = false - }; + //protected virtual async Task GetHtmlDocumentAsync(string url, CancellationToken cancellationToken) + //{ + // HtmlWeb web = new() + // { + // UsingCacheIfExists = false + // }; - //return web.Load(url); - - return await web.LoadFromWebAsync(url, cancellationToken); - } + // return await web.LoadFromWebAsync(url, cancellationToken); + //} } \ No newline at end of file diff --git a/MangaReader.Core/Metadata/SourceManga.cs b/MangaReader.Core/Metadata/SourceManga.cs index 3a5bbcc..9555b72 100644 --- a/MangaReader.Core/Metadata/SourceManga.cs +++ b/MangaReader.Core/Metadata/SourceManga.cs @@ -2,8 +2,8 @@ public class SourceManga { - public required string Title { get; set; } - public string? Description { get; set; } + public required SourceMangaTitle Title { get; set; } + public SourceMangaDescription? Description { get; set; } public List AlternateTitles { get; set; } = []; public SourceMangaContributor[] Contributors { get; set; } = []; public MangaStatus Status { get; set; } = MangaStatus.Unknown; diff --git a/MangaReader.Core/Metadata/SourceMangaDescription.cs b/MangaReader.Core/Metadata/SourceMangaDescription.cs new file mode 100644 index 0000000..dbfe107 --- /dev/null +++ b/MangaReader.Core/Metadata/SourceMangaDescription.cs @@ -0,0 +1,9 @@ +using MangaReader.Core.Common; + +namespace MangaReader.Core.Metadata; + +public class SourceMangaDescription +{ + public required string Name { get; set; } + public Language Language { get; set; } +} \ No newline at end of file diff --git a/MangaReader.Core/Metadata/SourceMangaTitle.cs b/MangaReader.Core/Metadata/SourceMangaTitle.cs index 634d338..4a2d05c 100644 --- a/MangaReader.Core/Metadata/SourceMangaTitle.cs +++ b/MangaReader.Core/Metadata/SourceMangaTitle.cs @@ -4,6 +4,6 @@ namespace MangaReader.Core.Metadata; public class SourceMangaTitle { - public required string Title { get; set; } + public required string Name { get; set; } public Language Language { get; set; } } \ No newline at end of file diff --git a/MangaReader.Core/Pipeline/MangaPipeline.cs b/MangaReader.Core/Pipeline/MangaPipeline.cs index 04498b9..c6bac7a 100644 --- a/MangaReader.Core/Pipeline/MangaPipeline.cs +++ b/MangaReader.Core/Pipeline/MangaPipeline.cs @@ -7,6 +7,12 @@ namespace MangaReader.Core.Pipeline; public partial class MangaPipeline(MangaContext context) : IMangaPipeline { + enum TitleType + { + Primary, + Secondary + } + public async Task RunAsync(MangaPipelineRequest request) { string sourceName = request.SourceName; @@ -17,10 +23,12 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline Manga manga = await GetOrAddMangaAsync(sourceManga); await AddMangaSourceAsync(sourceUrl, manga, source); + await AddTitleAsync(manga, sourceManga.Title, TitleType.Primary); + await AddDescriptionAsync(manga, sourceManga.Description); foreach (SourceMangaTitle alternateTitle in sourceManga.AlternateTitles) { - await AddTitleAsync(manga, alternateTitle); + await AddTitleAsync(manga, alternateTitle, TitleType.Secondary); } foreach (string genre in sourceManga.Genres) @@ -55,15 +63,15 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline private async Task GetOrAddMangaAsync(SourceManga sourceManga) { - Manga? manga = await context.Mangas.FirstOrDefaultAsync(manga => manga.Title == sourceManga.Title); + Manga? manga = await context.Mangas.FirstOrDefaultAsync(manga => + manga.Titles.Any(mangaTitle => mangaTitle.Name == sourceManga.Title.Name)); if (manga != null) return manga; manga = new() { - Title = sourceManga.Title, - Slug = GenerateSlug(sourceManga.Title), + Slug = GenerateSlug(sourceManga.Title.Name), }; context.Add(manga); @@ -104,10 +112,10 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline context.MangaSources.Add(mangaSource); } - private async Task AddTitleAsync(Manga manga, SourceMangaTitle sourceMangaTitle) + private async Task AddTitleAsync(Manga manga, SourceMangaTitle sourceMangaTitle, TitleType titleType) { MangaTitle? mangaTitle = await context.MangaTitles.FirstOrDefaultAsync(mt => - mt.Manga == manga && mt.Name == sourceMangaTitle.Title); + mt.Manga == manga && mt.Name == sourceMangaTitle.Name); if (mangaTitle != null) return; @@ -115,12 +123,35 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline mangaTitle = new() { Manga = manga, - Name = sourceMangaTitle.Title, + Name = sourceMangaTitle.Name, + Language = sourceMangaTitle.Language, + IsPrimary = titleType == TitleType.Primary }; context.MangaTitles.Add(mangaTitle); } + private async Task AddDescriptionAsync(Manga manga, SourceMangaDescription? sourceMangaDescription) + { + if (sourceMangaDescription == null) + return; + + MangaDescription? mangaDescription = await context.MangaDescriptions.FirstOrDefaultAsync(md => + md.Manga == manga && md.Name == sourceMangaDescription.Name); + + if (mangaDescription != null) + return; + + mangaDescription = new() + { + Manga = manga, + Name = sourceMangaDescription.Name, + Language = sourceMangaDescription.Language + }; + + context.MangaDescriptions.Add(mangaDescription); + } + private async Task LinkGenreAsync(Manga manga, string genreName) { Genre genre = await GetOrAddGenreAsync(genreName); @@ -156,28 +187,28 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline return genre; } - private async Task AddChapterAsync(Manga manga, SourceMangaChapter sourceeMangaChapter) + private async Task AddChapterAsync(Manga manga, SourceMangaChapter sourceMangaChapter) { - MangaChapter mangaChapter = await context.MangaChapters.FirstOrDefaultAsync(x => x.ChapterNumber == sourceeMangaChapter.Number) - ?? AddMangaChapter(manga, sourceeMangaChapter); + MangaChapter mangaChapter = await context.MangaChapters.FirstOrDefaultAsync(x => x.ChapterNumber == sourceMangaChapter.Number) + ?? AddMangaChapter(manga, sourceMangaChapter); - if (mangaChapter.VolumeNumber is null && sourceeMangaChapter.Volume is not null) + if (mangaChapter.VolumeNumber is null && sourceMangaChapter.Volume is not null) { - mangaChapter.VolumeNumber = sourceeMangaChapter.Volume; + mangaChapter.VolumeNumber = sourceMangaChapter.Volume; } - if (mangaChapter.Title is null && sourceeMangaChapter.Title is not null) + if (mangaChapter.Title is null && sourceMangaChapter.Title is not null) { - mangaChapter.Title = sourceeMangaChapter.Title; + mangaChapter.Title = sourceMangaChapter.Title; } } - private MangaChapter AddMangaChapter(Manga manga, SourceMangaChapter sourceeMangaChapter) + private MangaChapter AddMangaChapter(Manga manga, SourceMangaChapter sourceMangaChapter) { MangaChapter mangaChapter = new() { Manga = manga, - ChapterNumber = sourceeMangaChapter.Number + ChapterNumber = sourceMangaChapter.Number }; context.MangaChapters.Add(mangaChapter); diff --git a/MangaReader.Core/Search/MangaSearchProviderBase.cs b/MangaReader.Core/Search/MangaSearchProviderBase.cs index f3594d1..b7b93c0 100644 --- a/MangaReader.Core/Search/MangaSearchProviderBase.cs +++ b/MangaReader.Core/Search/MangaSearchProviderBase.cs @@ -1,4 +1,4 @@ -using MangaReader.Core.HttpService; +using MangaReader.Core.Http; using System.Text.Json; namespace MangaReader.Core.Search; diff --git a/MangaReader.Core/Sources/MangaDex/Api/MangaDexClient.cs b/MangaReader.Core/Sources/MangaDex/Api/MangaDexClient.cs index 2fbc41e..449ec80 100644 --- a/MangaReader.Core/Sources/MangaDex/Api/MangaDexClient.cs +++ b/MangaReader.Core/Sources/MangaDex/Api/MangaDexClient.cs @@ -1,4 +1,4 @@ -using MangaReader.Core.HttpService; +using MangaReader.Core.Http; using System.Text; using System.Text.Json; diff --git a/MangaReader.Core/Sources/MangaDex/Metadata/MangaDexMetadataProvider.cs b/MangaReader.Core/Sources/MangaDex/Metadata/MangaDexMetadataProvider.cs index 1126c48..3d15836 100644 --- a/MangaReader.Core/Sources/MangaDex/Metadata/MangaDexMetadataProvider.cs +++ b/MangaReader.Core/Sources/MangaDex/Metadata/MangaDexMetadataProvider.cs @@ -50,7 +50,16 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe return mangaGuid; } - private static string GetTitle(MangaAttributes attributes) + private static SourceMangaTitle GetTitle(MangaAttributes attributes) + { + return new() + { + Name = GetTileName(attributes), + Language = Language.English + }; + } + + private static string GetTileName(MangaAttributes attributes) { if (attributes.Title.TryGetValue("en", out string? title)) return title; @@ -81,7 +90,7 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe SourceMangaTitle sourceMangaTitle = new() { - Title = alternateTitle[alternateTitleKey], + Name = alternateTitle[alternateTitleKey], Language = language }; diff --git a/MangaReader.Core/Sources/MangaNato/Metadata/MangaNatoWebCrawler.cs b/MangaReader.Core/Sources/MangaNato/Metadata/MangaNatoWebCrawler.cs index e9ed1bd..25ca72e 100644 --- a/MangaReader.Core/Sources/MangaNato/Metadata/MangaNatoWebCrawler.cs +++ b/MangaReader.Core/Sources/MangaNato/Metadata/MangaNatoWebCrawler.cs @@ -1,23 +1,28 @@ using HtmlAgilityPack; using MangaReader.Core.Common; +using MangaReader.Core.Http; using MangaReader.Core.Metadata; using System.Text; using System.Web; namespace MangaReader.Core.Sources.MangaNato.Metadata; -public class MangaNatoWebCrawler : MangaWebCrawler +public class MangaNatoWebCrawler(IHtmlLoader htmlLoader) : MangaWebCrawler { public override string SourceId => "MangaNato"; public override async Task GetMangaAsync(string url, CancellationToken cancellationToken) { - HtmlDocument document = await GetHtmlDocumentAsync(url, cancellationToken); + HtmlDocument document = await htmlLoader.GetHtmlDocumentAsync(url, cancellationToken); MangaNatoMangaDocument node = new(document); SourceManga manga = new() { - Title = node.TitleNode?.InnerText ?? string.Empty, + Title = new() + { + Name = node.TitleNode?.InnerText ?? string.Empty, + Language = Language.Unknown + }, AlternateTitles = GetAlternateTitles(node.AlternateTitlesNode), Contributors = GetContributors(node.AuthorsNode), Status = GetStatus(node.StatusNode), @@ -26,7 +31,11 @@ public class MangaNatoWebCrawler : MangaWebCrawler RatingPercent = GetRatingPercent(node.AverageRatingNode, node.BestRatingNode), Votes = node.VotesNode != null ? int.Parse(node.VotesNode.InnerText) : 0, Views = GetViews(node.ViewsNode), - Description = GetTextFromNodes(node.StoryDescriptionTextNodes), + Description = new() + { + Name = GetTextFromNodes(node.StoryDescriptionTextNodes), + Language = Language.Unknown + }, Chapters = GetChapters(node.ChapterNodes) }; @@ -46,7 +55,7 @@ public class MangaNatoWebCrawler : MangaWebCrawler { SourceMangaTitle sourceMangaTitle = new() { - Title = title, + Name = title, Language = Language.Unknown }; diff --git a/MangaReader.Core/Sources/NatoManga/Api/NatoMangaClient.cs b/MangaReader.Core/Sources/NatoManga/Api/NatoMangaClient.cs index 56c7ebc..4d8c9be 100644 --- a/MangaReader.Core/Sources/NatoManga/Api/NatoMangaClient.cs +++ b/MangaReader.Core/Sources/NatoManga/Api/NatoMangaClient.cs @@ -1,4 +1,4 @@ -using MangaReader.Core.HttpService; +using MangaReader.Core.Http; using System.Globalization; using System.Text; using System.Text.Json; diff --git a/MangaReader.Core/Sources/NatoManga/Metadata/NatoMangaWebCrawler.cs b/MangaReader.Core/Sources/NatoManga/Metadata/NatoMangaWebCrawler.cs index b30f8e6..f56e5b8 100644 --- a/MangaReader.Core/Sources/NatoManga/Metadata/NatoMangaWebCrawler.cs +++ b/MangaReader.Core/Sources/NatoManga/Metadata/NatoMangaWebCrawler.cs @@ -1,20 +1,25 @@ using HtmlAgilityPack; +using MangaReader.Core.Http; using MangaReader.Core.Metadata; namespace MangaReader.Core.Sources.NatoManga.Metadata; -public class NatoMangaWebCrawler : MangaWebCrawler +public class NatoMangaWebCrawler(IHtmlLoader htmlLoader) : MangaWebCrawler { public override string SourceId => "NatoManga"; public override async Task GetMangaAsync(string url, CancellationToken cancellationToken) { - HtmlDocument document = await GetHtmlDocumentAsync(url, cancellationToken); + HtmlDocument document = await htmlLoader.GetHtmlDocumentAsync(url, cancellationToken); NatoMangaHtmlDocument node = new(document); SourceManga manga = new() { - Title = node.TitleNode?.InnerText ?? string.Empty, + Title = new() + { + Name = node.TitleNode?.InnerText ?? string.Empty, + Language = Common.Language.Unknown + }, Genres = GetGenres(node.GenresNode), Chapters = GetChapters(node.ChapterNodes) }; diff --git a/MangaReader.Tests/MangaReader.Tests.csproj b/MangaReader.Tests/MangaReader.Tests.csproj index f03cb00..f5613a0 100644 --- a/MangaReader.Tests/MangaReader.Tests.csproj +++ b/MangaReader.Tests/MangaReader.Tests.csproj @@ -13,16 +13,17 @@ + - + PreserveNewest - - + + PreserveNewest - + @@ -30,6 +31,7 @@ + @@ -59,4 +61,9 @@ + + + + + diff --git a/MangaReader.Tests/Pipeline/MangaPipelineTests.cs b/MangaReader.Tests/Pipeline/MangaPipelineTests.cs index c1e67e2..edf7c48 100644 --- a/MangaReader.Tests/Pipeline/MangaPipelineTests.cs +++ b/MangaReader.Tests/Pipeline/MangaPipelineTests.cs @@ -17,12 +17,16 @@ public class MangaPipelineTests(TestDbContextFactory factory) : IClassFixture mt.IsPrimary).ShouldHaveSingleItem(); + context.MangaTitles.Where(mt => mt.IsPrimary).First().Name.ShouldBe("Fullmetal Alchemist"); + context.MangaTitles.Where(mt => mt.IsPrimary).First().Language.ShouldBe(Language.English); context.Genres.Count().ShouldBe(2); context.MangaChapters.ShouldHaveSingleItem(); } diff --git a/MangaReader.Tests/Sources/MangaDex/Api/MangaDexClientTests.cs b/MangaReader.Tests/Sources/MangaDex/Api/MangaDexClientTests.cs index 5e1ac47..130eba2 100644 --- a/MangaReader.Tests/Sources/MangaDex/Api/MangaDexClientTests.cs +++ b/MangaReader.Tests/Sources/MangaDex/Api/MangaDexClientTests.cs @@ -1,4 +1,4 @@ -using MangaReader.Core.HttpService; +using MangaReader.Core.Http; using MangaReader.Core.Sources.MangaDex.Api; using MangaReader.Tests.Utilities; using NSubstitute; diff --git a/MangaReader.Tests/Sources/MangaDex/Metadata/MangaDexMetadataTests.cs b/MangaReader.Tests/Sources/MangaDex/Metadata/MangaDexMetadataTests.cs index 492d9f8..b59e9bb 100644 --- a/MangaReader.Tests/Sources/MangaDex/Metadata/MangaDexMetadataTests.cs +++ b/MangaReader.Tests/Sources/MangaDex/Metadata/MangaDexMetadataTests.cs @@ -229,23 +229,23 @@ public class MangaDexMetadataTests SourceManga? sourceManga = await metadataProvider.GetMangaAsync("https://mangadex.org/title/ee96e2b7-9af2-4864-9656-649f4d3b6fec/gals-can-t-be-kind-to-otaku", CancellationToken.None); sourceManga.ShouldNotBeNull(); - sourceManga.Title.ShouldBe("Gals Can’t Be Kind to Otaku!?"); + sourceManga.Title.Name.ShouldBe("Gals Can’t Be Kind to Otaku!?"); sourceManga.AlternateTitles.Count.ShouldBe(5); - sourceManga.AlternateTitles[0].Title.ShouldBe("オタクに優しいギャルはいない!?"); + sourceManga.AlternateTitles[0].Name.ShouldBe("オタクに優しいギャルはいない!?"); sourceManga.AlternateTitles[0].Language.ShouldBe(Language.Japanese); - sourceManga.AlternateTitles[1].Title.ShouldBe("Otaku ni Yasashii Gal wa Inai!?"); + sourceManga.AlternateTitles[1].Name.ShouldBe("Otaku ni Yasashii Gal wa Inai!?"); sourceManga.AlternateTitles[1].Language.ShouldBe(Language.Romaji); - sourceManga.AlternateTitles[2].Title.ShouldBe("Otaku ni Yasashii Gyaru ha Inai!?"); + sourceManga.AlternateTitles[2].Name.ShouldBe("Otaku ni Yasashii Gyaru ha Inai!?"); sourceManga.AlternateTitles[2].Language.ShouldBe(Language.Romaji); - sourceManga.AlternateTitles[3].Title.ShouldBe("Gal Can't Be Kind to Otaku!?"); + sourceManga.AlternateTitles[3].Name.ShouldBe("Gal Can't Be Kind to Otaku!?"); sourceManga.AlternateTitles[3].Language.ShouldBe(Language.English); - sourceManga.AlternateTitles[4].Title.ShouldBe("Gals Can't Be Kind To A Geek!?"); + sourceManga.AlternateTitles[4].Name.ShouldBe("Gals Can't Be Kind To A Geek!?"); sourceManga.AlternateTitles[4].Language.ShouldBe(Language.English); sourceManga.Genres.Count.ShouldBe(5); diff --git a/MangaReader.Tests/WebCrawlers/Samples/MangaNato - Please Go Home, Akutsu-San!.htm b/MangaReader.Tests/Sources/MangaNato/Metadata/Manga-Response.html similarity index 100% rename from MangaReader.Tests/WebCrawlers/Samples/MangaNato - Please Go Home, Akutsu-San!.htm rename to MangaReader.Tests/Sources/MangaNato/Metadata/Manga-Response.html diff --git a/MangaReader.Tests/WebCrawlers/UnitTest1.cs b/MangaReader.Tests/Sources/MangaNato/Metadata/MangaNatoMetadataTests.cs similarity index 58% rename from MangaReader.Tests/WebCrawlers/UnitTest1.cs rename to MangaReader.Tests/Sources/MangaNato/Metadata/MangaNatoMetadataTests.cs index 8724b9c..5993137 100644 --- a/MangaReader.Tests/WebCrawlers/UnitTest1.cs +++ b/MangaReader.Tests/Sources/MangaNato/Metadata/MangaNatoMetadataTests.cs @@ -1,47 +1,35 @@ -using HtmlAgilityPack; +using MangaReader.Core.Http; using MangaReader.Core.Metadata; using MangaReader.Core.Sources.MangaNato.Metadata; +using MangaReader.Tests.Utilities; +using NSubstitute; using Shouldly; using System.Data; -using System.Xml.Linq; -namespace MangaReader.Tests.WebCrawlers; +namespace MangaReader.Tests.Sources.MangaNato.Metadata; -public class UnitTest1 +public class MangaNatoMetadataTests { - class TestMangaNatoWebCrawler : MangaNatoWebCrawler - { - protected override Task GetHtmlDocumentAsync(string url, CancellationToken cancellationToken) - { - HtmlWeb web = new() - { - UsingCacheIfExists = false - }; - - return Task.FromResult(web.Load(url)); - } - } - - private readonly string samplesPath; - private readonly string mangaNatoSampleFilePath; - - public UnitTest1() - { - samplesPath = Path.Combine(AppContext.BaseDirectory, "WebCrawlers", "Samples"); - mangaNatoSampleFilePath = Path.Combine(samplesPath, "MangaNato - Please Go Home, Akutsu-San!.htm"); - } - [Fact] public async Task Get_Manga() { - var webCrawler = new TestMangaNatoWebCrawler(); - var manga = await webCrawler.GetMangaAsync(mangaNatoSampleFilePath, CancellationToken.None); + string mangaHtml = await ReadJsonResourceAsync("Manga-Response.html"); + + IHttpService httpService = Substitute.For(); + + httpService.GetStringAsync(Arg.Any(), CancellationToken.None) + .Returns(Task.FromResult(mangaHtml)); + + HtmlLoader htmlLoader = new(httpService); + + MangaNatoWebCrawler webCrawler = new(htmlLoader); + SourceManga? manga = await webCrawler.GetMangaAsync("/test-url", CancellationToken.None); manga.ShouldNotBeNull(); - manga.Title.ShouldBe("Please Go Home, Akutsu-San!"); + manga.Title.Name.ShouldBe("Please Go Home, Akutsu-San!"); - manga.AlternateTitles.Select(x => x.Title).ShouldBe([ + manga.AlternateTitles.Select(x => x.Name).ShouldBe([ "Kaette kudasai! Akutsu-san", "Yankee Musume", "ヤンキー娘", @@ -62,8 +50,8 @@ public class UnitTest1 manga.Votes.ShouldBe(15979); //manga.Description.ShouldStartWith("Ooyama-kun normally doesn’t get involved with Akutsu-san, a delinquent girl in his class"); - manga.Description.ShouldStartWith("Ooyama-kun normally doesn’t get involved with Akutsu-san, a delinquent girl in his class"); - manga.Description.ShouldEndWith("Artist's Pixiv: https://www.pixiv.net/member.php?id=133935"); + manga.Description?.Name.ShouldStartWith("Ooyama-kun normally doesn’t get involved with Akutsu-san, a delinquent girl in his class"); + manga.Description?.Name.ShouldEndWith("Artist's Pixiv: https://www.pixiv.net/member.php?id=133935"); manga.Chapters.Count.ShouldBe(236); @@ -79,4 +67,9 @@ public class UnitTest1 manga.Chapters[235].Views.ShouldBe(232_200); manga.Chapters[235].UploadDate.ShouldBe(new DateTime(2021, 8, 24, 1, 8, 0)); } + + private static async Task ReadJsonResourceAsync(string resourceName) + { + return await ResourceHelper.ReadJsonResourceAsync($"MangaReader.Tests.Sources.MangaNato.Metadata.{resourceName}"); + } } \ No newline at end of file diff --git a/MangaReader.Tests/Sources/NatoManga/Api/NatoMangaClientTests.cs b/MangaReader.Tests/Sources/NatoManga/Api/NatoMangaClientTests.cs index dc716e0..5e81215 100644 --- a/MangaReader.Tests/Sources/NatoManga/Api/NatoMangaClientTests.cs +++ b/MangaReader.Tests/Sources/NatoManga/Api/NatoMangaClientTests.cs @@ -1,4 +1,4 @@ -using MangaReader.Core.HttpService; +using MangaReader.Core.Http; using MangaReader.Core.Sources.NatoManga.Api; using MangaReader.Tests.Utilities; using NSubstitute; @@ -34,6 +34,9 @@ public class NatoMangaClientTests httpService.GetStringAsync(Arg.Any(), CancellationToken.None) .Returns(Task.FromResult(searchResultJson)); + httpService.GetStringAsync(Arg.Any(), Arg.Any>(), CancellationToken.None) + .Returns(Task.FromResult(searchResultJson)); + NatoMangaClient natoMangaClient = new(httpService); NatoMangaSearchResult[] searchResults = await natoMangaClient.SearchAsync("Gal Can't Be Kind", CancellationToken.None); diff --git a/MangaReader.Tests/Sources/NatoManga/Metadata/Manga-Chapter-Response.html b/MangaReader.Tests/Sources/NatoManga/Metadata/Manga-Chapter-Response.html new file mode 100644 index 0000000..c744c2e --- /dev/null +++ b/MangaReader.Tests/Sources/NatoManga/Metadata/Manga-Chapter-Response.html @@ -0,0 +1,1697 @@ + + + + + + + + Read Gal Can’t Be Kind to Otaku!? Manga Chapter 69 Free Online On MangaNato + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Manga Online + +
+ +
+ + + +
+ +

Gal Can’t Be Kind to Otaku!?: Chapter 69

+
+
+ + +
+ +
+
+ +
+
+ +
+ +
+
+ + + + + +
+

Gal Can’t Be Kind to Otaku!? Chapter 69

+

You're reading Gal Can’t Be Kind to Otaku!? Chapter 69 at MangaNato.

+

+ Click the + + + + + + button now to stay updated on the latest chapters on MangaKakalot!💡Press F11 button to read manga in full-screen(PC-only). + It will be so grateful if you let Mangakakalot be your favorite manga site. We hope you'll come join us and become a manga reader in this community! Have a beautiful day! +

+
+ + + IMAGES SERVER: + + 1 + + + Report Error + + +
+
+ + +
+ Gal Can’t Be Kind to Otaku!? Chapter 69 page 1 - MangaNatoGal Can’t Be Kind to Otaku!? Chapter 69 page 2 - MangaNatoGal Can’t Be Kind to Otaku!? Chapter 69 page 3 - MangaNatoGal Can’t Be Kind to Otaku!? Chapter 69 page 4 - MangaNatoGal Can’t Be Kind to Otaku!? Chapter 69 page 5 - MangaNatoGal Can’t Be Kind to Otaku!? Chapter 69 page 6 - MangaNatoGal Can’t Be Kind to Otaku!? Chapter 69 page 7 - MangaNatoGal Can’t Be Kind to Otaku!? Chapter 69 page 8 - MangaNatoGal Can’t Be Kind to Otaku!? Chapter 69 page 9 - MangaNatoGal Can’t Be Kind to Otaku!? Chapter 69 page 10 - MangaNatoGal Can’t Be Kind to Otaku!? Chapter 69 page 11 - MangaNatoGal Can’t Be Kind to Otaku!? Chapter 69 page 12 - MangaNatoGal Can’t Be Kind to Otaku!? Chapter 69 page 13 - MangaNato +
+ +
+
+ + Go Home + +
+
+ + +
+ +
+
+
+ +
+ + + +
+
+ +
+

+ You just finished reading Gal Can’t Be Kind to Otaku!? online. The Bookmark + button is a very simple way to get notifications when your favorite manga have new updates. It's very useful + to anyone who loves reading manga. Let's us guide you to find your best manga to + read. And if you find any errors, let us know so we can fix it as soon as possible! +

+

You can support us by leaving comments or just a click on the Like button!

+ +
+ + + + + +
+
+
+
+ +
+ + +

+ Gal Can’t Be Kind to Otaku!? Chapter 69 summary +

+

+ You're reading Gal Can’t Be Kind to Otaku!?. This manga has been translated by Updating. Author: + Norishiro-chan,Sakana Uozimi already has 2,038,955 views. +

+

+ If you want to read free manga, come visit us at anytime. We promise you that we will always bring you the + latest, new and hot manga everyday. In case you don't know, Mangakakalot is a very cool responsive website + and mobile-friendly, which means the images can be auto-resize to fit your pc or mobile screen. You can + experience it by using your smartphone and + read manga online + right now. It's manga time!! +

+
+
+ + +
+ +
+ +
+ + + +
+ +
+

content notification

+
CANCEL
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MangaReader.Tests/WebCrawlers/NatoManga/SampleMangaPage.html b/MangaReader.Tests/Sources/NatoManga/Metadata/Manga-Response.html similarity index 100% rename from MangaReader.Tests/WebCrawlers/NatoManga/SampleMangaPage.html rename to MangaReader.Tests/Sources/NatoManga/Metadata/Manga-Response.html diff --git a/MangaReader.Tests/WebCrawlers/NatoManga/NatoMangaWebCrawlerTests.cs b/MangaReader.Tests/Sources/NatoManga/Metadata/NatoMangaWebCrawlerTests.cs similarity index 68% rename from MangaReader.Tests/WebCrawlers/NatoManga/NatoMangaWebCrawlerTests.cs rename to MangaReader.Tests/Sources/NatoManga/Metadata/NatoMangaWebCrawlerTests.cs index 56eee6b..1cbc5af 100644 --- a/MangaReader.Tests/WebCrawlers/NatoManga/NatoMangaWebCrawlerTests.cs +++ b/MangaReader.Tests/Sources/NatoManga/Metadata/NatoMangaWebCrawlerTests.cs @@ -1,35 +1,32 @@ -using HtmlAgilityPack; +using MangaReader.Core.Http; +using MangaReader.Core.Metadata; using MangaReader.Core.Sources.NatoManga.Metadata; +using MangaReader.Tests.Utilities; +using NSubstitute; using Shouldly; -namespace MangaReader.Tests.WebCrawlers.NatoManga; +namespace MangaReader.Tests.Sources.NatoManga.Metadata; public class NatoMangaWebCrawlerTests { - class TestNatoMangaWebCrawler : NatoMangaWebCrawler - { - protected override Task GetHtmlDocumentAsync(string url, CancellationToken cancellationToken) - { - HtmlWeb web = new() - { - UsingCacheIfExists = false - }; - - return Task.FromResult(web.Load(url)); - } - } - [Fact] public async Task Get_Manga() { - string sampleFilePath = Path.Combine(AppContext.BaseDirectory, "WebCrawlers", "NatoManga", "SampleMangaPage.html"); + string mangaHtml = await ReadJsonResourceAsync("Manga-Response.html"); - var webCrawler = new TestNatoMangaWebCrawler(); - var manga = await webCrawler.GetMangaAsync(sampleFilePath, CancellationToken.None); + IHttpService httpService = Substitute.For(); + + httpService.GetStringAsync(Arg.Any(), CancellationToken.None) + .Returns(Task.FromResult(mangaHtml)); + + HtmlLoader htmlLoader = new(httpService); + + NatoMangaWebCrawler webCrawler = new(htmlLoader); + SourceManga? manga = await webCrawler.GetMangaAsync("/test-url", CancellationToken.None); manga.ShouldNotBeNull(); - manga.Title.ShouldBe("Gal Can’t Be Kind to Otaku!?"); + manga.Title.Name.ShouldBe("Gal Can’t Be Kind to Otaku!?"); //manga.AlternateTitles.ShouldBe([ // "Kaette kudasai! Akutsu-san", @@ -63,4 +60,9 @@ public class NatoMangaWebCrawlerTests //manga.Chapters[235].Views.ShouldBe(232_200); //manga.Chapters[235].UploadDate.ShouldBe(new DateTime(2021, 8, 24, 1, 8, 0)); } + + private static async Task ReadJsonResourceAsync(string resourceName) + { + return await ResourceHelper.ReadJsonResourceAsync($"MangaReader.Tests.Sources.NatoManga.Metadata.{resourceName}"); + } } \ No newline at end of file