Added abstraction layeer IHtmlLoader. Finished reorganizing test project folder structure.
This commit is contained in:
@@ -4,11 +4,10 @@ public class Manga
|
|||||||
{
|
{
|
||||||
public int MangaId { get; set; }
|
public int MangaId { get; set; }
|
||||||
public required string Slug { get; set; }
|
public required string Slug { get; set; }
|
||||||
public required string Title { get; set; }
|
|
||||||
public string? Description { get; set; }
|
|
||||||
|
|
||||||
public virtual ICollection<MangaCover> Covers { get; set; } = [];
|
public virtual ICollection<MangaCover> Covers { get; set; } = [];
|
||||||
public virtual ICollection<MangaTitle> Titles { get; set; } = [];
|
public virtual ICollection<MangaTitle> Titles { get; set; } = [];
|
||||||
|
public virtual ICollection<MangaDescription> Descriptions { get; set; } = [];
|
||||||
public virtual ICollection<MangaSource> Sources { get; set; } = [];
|
public virtual ICollection<MangaSource> Sources { get; set; } = [];
|
||||||
public virtual ICollection<MangaContributor> Contributors { get; set; } = [];
|
public virtual ICollection<MangaContributor> Contributors { get; set; } = [];
|
||||||
public virtual ICollection<MangaGenre> Genres { get; set; } = [];
|
public virtual ICollection<MangaGenre> Genres { get; set; } = [];
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public class MangaContext(DbContextOptions options) : DbContext(options)
|
|||||||
public DbSet<Manga> Mangas { get; set; }
|
public DbSet<Manga> Mangas { get; set; }
|
||||||
public DbSet<MangaCover> MangaCovers { get; set; }
|
public DbSet<MangaCover> MangaCovers { get; set; }
|
||||||
public DbSet<MangaTitle> MangaTitles { get; set; }
|
public DbSet<MangaTitle> MangaTitles { get; set; }
|
||||||
|
public DbSet<MangaDescription> MangaDescriptions { get; set; }
|
||||||
public DbSet<Source> Sources { get; set; }
|
public DbSet<Source> Sources { get; set; }
|
||||||
public DbSet<MangaSource> MangaSources { get; set; }
|
public DbSet<MangaSource> MangaSources { get; set; }
|
||||||
public DbSet<Contributor> Contributors { get; set; }
|
public DbSet<Contributor> Contributors { get; set; }
|
||||||
@@ -24,6 +25,7 @@ public class MangaContext(DbContextOptions options) : DbContext(options)
|
|||||||
ConfigureManga(modelBuilder);
|
ConfigureManga(modelBuilder);
|
||||||
ConfigureMangaCover(modelBuilder);
|
ConfigureMangaCover(modelBuilder);
|
||||||
ConfigureMangaTitle(modelBuilder);
|
ConfigureMangaTitle(modelBuilder);
|
||||||
|
ConfigureMangaDescription(modelBuilder);
|
||||||
ConfigureSource(modelBuilder);
|
ConfigureSource(modelBuilder);
|
||||||
ConfigureMangaSource(modelBuilder);
|
ConfigureMangaSource(modelBuilder);
|
||||||
ConfigureContributor(modelBuilder);
|
ConfigureContributor(modelBuilder);
|
||||||
@@ -41,9 +43,9 @@ public class MangaContext(DbContextOptions options) : DbContext(options)
|
|||||||
.Entity<Manga>()
|
.Entity<Manga>()
|
||||||
.HasKey(x => x.MangaId);
|
.HasKey(x => x.MangaId);
|
||||||
|
|
||||||
modelBuilder.Entity<Manga>()
|
//modelBuilder.Entity<Manga>()
|
||||||
.HasIndex(x => x.Title)
|
// .HasIndex(x => x.Title)
|
||||||
.IsUnique();
|
// .IsUnique();
|
||||||
|
|
||||||
modelBuilder.Entity<Manga>()
|
modelBuilder.Entity<Manga>()
|
||||||
.HasIndex(x => x.Slug)
|
.HasIndex(x => x.Slug)
|
||||||
@@ -81,7 +83,15 @@ public class MangaContext(DbContextOptions options) : DbContext(options)
|
|||||||
.HasKey(mangaTitle => mangaTitle.MangaTitleId);
|
.HasKey(mangaTitle => mangaTitle.MangaTitleId);
|
||||||
|
|
||||||
modelBuilder.Entity<MangaTitle>()
|
modelBuilder.Entity<MangaTitle>()
|
||||||
.HasIndex(mangaTitle => new { mangaTitle.MangaId, mangaTitle.Name })
|
.Property(mt => mt.Name)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
modelBuilder.Entity<MangaTitle>()
|
||||||
|
.Property(mt => mt.Language)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
modelBuilder.Entity<MangaTitle>()
|
||||||
|
.HasIndex(mangaTitle => new { mangaTitle.MangaId, mangaTitle.Name, mangaTitle.Language })
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
modelBuilder
|
modelBuilder
|
||||||
@@ -96,6 +106,36 @@ public class MangaContext(DbContextOptions options) : DbContext(options)
|
|||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ConfigureMangaDescription(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.Entity<MangaDescription>()
|
||||||
|
.HasKey(mangaTitle => mangaTitle.MangaTitleId);
|
||||||
|
|
||||||
|
modelBuilder.Entity<MangaDescription>()
|
||||||
|
.Property(mt => mt.Name)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
modelBuilder.Entity<MangaDescription>()
|
||||||
|
.Property(mt => mt.Language)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
modelBuilder.Entity<MangaDescription>()
|
||||||
|
.HasIndex(mangaTitle => new { mangaTitle.MangaId, mangaTitle.Name, mangaTitle.Language })
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
modelBuilder
|
||||||
|
.Entity<MangaDescription>()
|
||||||
|
.HasIndex(mangaTitle => mangaTitle.Name);
|
||||||
|
|
||||||
|
modelBuilder
|
||||||
|
.Entity<MangaDescription>()
|
||||||
|
.HasOne(x => x.Manga)
|
||||||
|
.WithMany(x => x.Descriptions)
|
||||||
|
.HasForeignKey(x => x.MangaId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
}
|
||||||
|
|
||||||
private static void ConfigureSource(ModelBuilder modelBuilder)
|
private static void ConfigureSource(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
modelBuilder
|
modelBuilder
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace MangaReader.Core.Data;
|
using MangaReader.Core.Common;
|
||||||
|
|
||||||
|
namespace MangaReader.Core.Data;
|
||||||
|
|
||||||
public class MangaTitle
|
public class MangaTitle
|
||||||
{
|
{
|
||||||
@@ -8,5 +10,6 @@ public class MangaTitle
|
|||||||
public required Manga Manga { get; set; }
|
public required Manga Manga { get; set; }
|
||||||
|
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
public TitleType TitleType { get; set; }
|
public required Language Language { get; set; }
|
||||||
|
public bool IsPrimary { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
namespace MangaReader.Core.Data;
|
namespace MangaReader.Core.Data;
|
||||||
|
|
||||||
public enum TitleType
|
//public enum TitleType
|
||||||
{
|
//{
|
||||||
Primary,
|
// Primary,
|
||||||
OfficialTranslation,
|
// OfficialTranslation,
|
||||||
FanTranslation,
|
// FanTranslation,
|
||||||
Synonym,
|
// Synonym,
|
||||||
Abbreviation,
|
// Abbreviation,
|
||||||
Romaji,
|
// Romaji,
|
||||||
Japanese
|
// Japanese
|
||||||
}
|
//}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using MangaReader.Core.HttpService;
|
using MangaReader.Core.Http;
|
||||||
using MangaReader.Core.Metadata;
|
using MangaReader.Core.Metadata;
|
||||||
using MangaReader.Core.Search;
|
using MangaReader.Core.Search;
|
||||||
using MangaReader.Core.Sources.MangaDex.Api;
|
using MangaReader.Core.Sources.MangaDex.Api;
|
||||||
|
|||||||
16
MangaReader.Core/Http/HtmlLoader.cs
Normal file
16
MangaReader.Core/Http/HtmlLoader.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using HtmlAgilityPack;
|
||||||
|
|
||||||
|
namespace MangaReader.Core.Http;
|
||||||
|
|
||||||
|
public class HtmlLoader(IHttpService httpService) : IHtmlLoader
|
||||||
|
{
|
||||||
|
public async Task<HtmlDocument> GetHtmlDocumentAsync(string url, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
string html = await httpService.GetStringAsync(url, cancellationToken);
|
||||||
|
|
||||||
|
HtmlDocument doc = new();
|
||||||
|
doc.LoadHtml(html);
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace MangaReader.Core.HttpService;
|
namespace MangaReader.Core.Http;
|
||||||
|
|
||||||
public class HttpService(HttpClient httpClient) : IHttpService
|
public class HttpService(HttpClient httpClient) : IHttpService
|
||||||
{
|
{
|
||||||
8
MangaReader.Core/Http/IHtmlLoader.cs
Normal file
8
MangaReader.Core/Http/IHtmlLoader.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using HtmlAgilityPack;
|
||||||
|
|
||||||
|
namespace MangaReader.Core.Http;
|
||||||
|
|
||||||
|
public interface IHtmlLoader
|
||||||
|
{
|
||||||
|
Task<HtmlDocument> GetHtmlDocumentAsync(string url, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace MangaReader.Core.HttpService;
|
namespace MangaReader.Core.Http;
|
||||||
|
|
||||||
public interface IHttpService
|
public interface IHttpService
|
||||||
{
|
{
|
||||||
@@ -1,21 +1,17 @@
|
|||||||
using HtmlAgilityPack;
|
namespace MangaReader.Core.Metadata;
|
||||||
|
|
||||||
namespace MangaReader.Core.Metadata;
|
|
||||||
|
|
||||||
public abstract class MangaWebCrawler : IMangaMetadataProvider
|
public abstract class MangaWebCrawler : IMangaMetadataProvider
|
||||||
{
|
{
|
||||||
public abstract string SourceId { get; }
|
public abstract string SourceId { get; }
|
||||||
public abstract Task<SourceManga?> GetMangaAsync(string url, CancellationToken cancellationToken);
|
public abstract Task<SourceManga?> GetMangaAsync(string url, CancellationToken cancellationToken);
|
||||||
|
|
||||||
protected virtual async Task<HtmlDocument> GetHtmlDocumentAsync(string url, CancellationToken cancellationToken)
|
//protected virtual async Task<HtmlDocument> GetHtmlDocumentAsync(string url, CancellationToken cancellationToken)
|
||||||
{
|
//{
|
||||||
HtmlWeb web = new()
|
// HtmlWeb web = new()
|
||||||
{
|
// {
|
||||||
UsingCacheIfExists = false
|
// UsingCacheIfExists = false
|
||||||
};
|
// };
|
||||||
|
|
||||||
//return web.Load(url);
|
// return await web.LoadFromWebAsync(url, cancellationToken);
|
||||||
|
//}
|
||||||
return await web.LoadFromWebAsync(url, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
public class SourceManga
|
public class SourceManga
|
||||||
{
|
{
|
||||||
public required string Title { get; set; }
|
public required SourceMangaTitle Title { get; set; }
|
||||||
public string? Description { get; set; }
|
public SourceMangaDescription? Description { get; set; }
|
||||||
public List<SourceMangaTitle> AlternateTitles { get; set; } = [];
|
public List<SourceMangaTitle> AlternateTitles { get; set; } = [];
|
||||||
public SourceMangaContributor[] Contributors { get; set; } = [];
|
public SourceMangaContributor[] Contributors { get; set; } = [];
|
||||||
public MangaStatus Status { get; set; } = MangaStatus.Unknown;
|
public MangaStatus Status { get; set; } = MangaStatus.Unknown;
|
||||||
|
|||||||
9
MangaReader.Core/Metadata/SourceMangaDescription.cs
Normal file
9
MangaReader.Core/Metadata/SourceMangaDescription.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
@@ -4,6 +4,6 @@ namespace MangaReader.Core.Metadata;
|
|||||||
|
|
||||||
public class SourceMangaTitle
|
public class SourceMangaTitle
|
||||||
{
|
{
|
||||||
public required string Title { get; set; }
|
public required string Name { get; set; }
|
||||||
public Language Language { get; set; }
|
public Language Language { get; set; }
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,12 @@ namespace MangaReader.Core.Pipeline;
|
|||||||
|
|
||||||
public partial class MangaPipeline(MangaContext context) : IMangaPipeline
|
public partial class MangaPipeline(MangaContext context) : IMangaPipeline
|
||||||
{
|
{
|
||||||
|
enum TitleType
|
||||||
|
{
|
||||||
|
Primary,
|
||||||
|
Secondary
|
||||||
|
}
|
||||||
|
|
||||||
public async Task RunAsync(MangaPipelineRequest request)
|
public async Task RunAsync(MangaPipelineRequest request)
|
||||||
{
|
{
|
||||||
string sourceName = request.SourceName;
|
string sourceName = request.SourceName;
|
||||||
@@ -17,10 +23,12 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline
|
|||||||
Manga manga = await GetOrAddMangaAsync(sourceManga);
|
Manga manga = await GetOrAddMangaAsync(sourceManga);
|
||||||
|
|
||||||
await AddMangaSourceAsync(sourceUrl, manga, source);
|
await AddMangaSourceAsync(sourceUrl, manga, source);
|
||||||
|
await AddTitleAsync(manga, sourceManga.Title, TitleType.Primary);
|
||||||
|
await AddDescriptionAsync(manga, sourceManga.Description);
|
||||||
|
|
||||||
foreach (SourceMangaTitle alternateTitle in sourceManga.AlternateTitles)
|
foreach (SourceMangaTitle alternateTitle in sourceManga.AlternateTitles)
|
||||||
{
|
{
|
||||||
await AddTitleAsync(manga, alternateTitle);
|
await AddTitleAsync(manga, alternateTitle, TitleType.Secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (string genre in sourceManga.Genres)
|
foreach (string genre in sourceManga.Genres)
|
||||||
@@ -55,15 +63,15 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline
|
|||||||
|
|
||||||
private async Task<Manga> GetOrAddMangaAsync(SourceManga sourceManga)
|
private async Task<Manga> 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)
|
if (manga != null)
|
||||||
return manga;
|
return manga;
|
||||||
|
|
||||||
manga = new()
|
manga = new()
|
||||||
{
|
{
|
||||||
Title = sourceManga.Title,
|
Slug = GenerateSlug(sourceManga.Title.Name),
|
||||||
Slug = GenerateSlug(sourceManga.Title),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
context.Add(manga);
|
context.Add(manga);
|
||||||
@@ -104,10 +112,10 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline
|
|||||||
context.MangaSources.Add(mangaSource);
|
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 =>
|
MangaTitle? mangaTitle = await context.MangaTitles.FirstOrDefaultAsync(mt =>
|
||||||
mt.Manga == manga && mt.Name == sourceMangaTitle.Title);
|
mt.Manga == manga && mt.Name == sourceMangaTitle.Name);
|
||||||
|
|
||||||
if (mangaTitle != null)
|
if (mangaTitle != null)
|
||||||
return;
|
return;
|
||||||
@@ -115,12 +123,35 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline
|
|||||||
mangaTitle = new()
|
mangaTitle = new()
|
||||||
{
|
{
|
||||||
Manga = manga,
|
Manga = manga,
|
||||||
Name = sourceMangaTitle.Title,
|
Name = sourceMangaTitle.Name,
|
||||||
|
Language = sourceMangaTitle.Language,
|
||||||
|
IsPrimary = titleType == TitleType.Primary
|
||||||
};
|
};
|
||||||
|
|
||||||
context.MangaTitles.Add(mangaTitle);
|
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)
|
private async Task LinkGenreAsync(Manga manga, string genreName)
|
||||||
{
|
{
|
||||||
Genre genre = await GetOrAddGenreAsync(genreName);
|
Genre genre = await GetOrAddGenreAsync(genreName);
|
||||||
@@ -156,28 +187,28 @@ public partial class MangaPipeline(MangaContext context) : IMangaPipeline
|
|||||||
return genre;
|
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)
|
MangaChapter mangaChapter = await context.MangaChapters.FirstOrDefaultAsync(x => x.ChapterNumber == sourceMangaChapter.Number)
|
||||||
?? AddMangaChapter(manga, sourceeMangaChapter);
|
?? 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()
|
MangaChapter mangaChapter = new()
|
||||||
{
|
{
|
||||||
Manga = manga,
|
Manga = manga,
|
||||||
ChapterNumber = sourceeMangaChapter.Number
|
ChapterNumber = sourceMangaChapter.Number
|
||||||
};
|
};
|
||||||
|
|
||||||
context.MangaChapters.Add(mangaChapter);
|
context.MangaChapters.Add(mangaChapter);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using MangaReader.Core.HttpService;
|
using MangaReader.Core.Http;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace MangaReader.Core.Search;
|
namespace MangaReader.Core.Search;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using MangaReader.Core.HttpService;
|
using MangaReader.Core.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,16 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe
|
|||||||
return mangaGuid;
|
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))
|
if (attributes.Title.TryGetValue("en", out string? title))
|
||||||
return title;
|
return title;
|
||||||
@@ -81,7 +90,7 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe
|
|||||||
|
|
||||||
SourceMangaTitle sourceMangaTitle = new()
|
SourceMangaTitle sourceMangaTitle = new()
|
||||||
{
|
{
|
||||||
Title = alternateTitle[alternateTitleKey],
|
Name = alternateTitle[alternateTitleKey],
|
||||||
Language = language
|
Language = language
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
using HtmlAgilityPack;
|
using HtmlAgilityPack;
|
||||||
using MangaReader.Core.Common;
|
using MangaReader.Core.Common;
|
||||||
|
using MangaReader.Core.Http;
|
||||||
using MangaReader.Core.Metadata;
|
using MangaReader.Core.Metadata;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
|
|
||||||
namespace MangaReader.Core.Sources.MangaNato.Metadata;
|
namespace MangaReader.Core.Sources.MangaNato.Metadata;
|
||||||
|
|
||||||
public class MangaNatoWebCrawler : MangaWebCrawler
|
public class MangaNatoWebCrawler(IHtmlLoader htmlLoader) : MangaWebCrawler
|
||||||
{
|
{
|
||||||
public override string SourceId => "MangaNato";
|
public override string SourceId => "MangaNato";
|
||||||
|
|
||||||
public override async Task<SourceManga?> GetMangaAsync(string url, CancellationToken cancellationToken)
|
public override async Task<SourceManga?> GetMangaAsync(string url, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
HtmlDocument document = await GetHtmlDocumentAsync(url, cancellationToken);
|
HtmlDocument document = await htmlLoader.GetHtmlDocumentAsync(url, cancellationToken);
|
||||||
MangaNatoMangaDocument node = new(document);
|
MangaNatoMangaDocument node = new(document);
|
||||||
|
|
||||||
SourceManga manga = new()
|
SourceManga manga = new()
|
||||||
{
|
{
|
||||||
Title = node.TitleNode?.InnerText ?? string.Empty,
|
Title = new()
|
||||||
|
{
|
||||||
|
Name = node.TitleNode?.InnerText ?? string.Empty,
|
||||||
|
Language = Language.Unknown
|
||||||
|
},
|
||||||
AlternateTitles = GetAlternateTitles(node.AlternateTitlesNode),
|
AlternateTitles = GetAlternateTitles(node.AlternateTitlesNode),
|
||||||
Contributors = GetContributors(node.AuthorsNode),
|
Contributors = GetContributors(node.AuthorsNode),
|
||||||
Status = GetStatus(node.StatusNode),
|
Status = GetStatus(node.StatusNode),
|
||||||
@@ -26,7 +31,11 @@ public class MangaNatoWebCrawler : MangaWebCrawler
|
|||||||
RatingPercent = GetRatingPercent(node.AverageRatingNode, node.BestRatingNode),
|
RatingPercent = GetRatingPercent(node.AverageRatingNode, node.BestRatingNode),
|
||||||
Votes = node.VotesNode != null ? int.Parse(node.VotesNode.InnerText) : 0,
|
Votes = node.VotesNode != null ? int.Parse(node.VotesNode.InnerText) : 0,
|
||||||
Views = GetViews(node.ViewsNode),
|
Views = GetViews(node.ViewsNode),
|
||||||
Description = GetTextFromNodes(node.StoryDescriptionTextNodes),
|
Description = new()
|
||||||
|
{
|
||||||
|
Name = GetTextFromNodes(node.StoryDescriptionTextNodes),
|
||||||
|
Language = Language.Unknown
|
||||||
|
},
|
||||||
Chapters = GetChapters(node.ChapterNodes)
|
Chapters = GetChapters(node.ChapterNodes)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,7 +55,7 @@ public class MangaNatoWebCrawler : MangaWebCrawler
|
|||||||
{
|
{
|
||||||
SourceMangaTitle sourceMangaTitle = new()
|
SourceMangaTitle sourceMangaTitle = new()
|
||||||
{
|
{
|
||||||
Title = title,
|
Name = title,
|
||||||
Language = Language.Unknown
|
Language = Language.Unknown
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using MangaReader.Core.HttpService;
|
using MangaReader.Core.Http;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
using HtmlAgilityPack;
|
using HtmlAgilityPack;
|
||||||
|
using MangaReader.Core.Http;
|
||||||
using MangaReader.Core.Metadata;
|
using MangaReader.Core.Metadata;
|
||||||
|
|
||||||
namespace MangaReader.Core.Sources.NatoManga.Metadata;
|
namespace MangaReader.Core.Sources.NatoManga.Metadata;
|
||||||
|
|
||||||
public class NatoMangaWebCrawler : MangaWebCrawler
|
public class NatoMangaWebCrawler(IHtmlLoader htmlLoader) : MangaWebCrawler
|
||||||
{
|
{
|
||||||
public override string SourceId => "NatoManga";
|
public override string SourceId => "NatoManga";
|
||||||
|
|
||||||
public override async Task<SourceManga?> GetMangaAsync(string url, CancellationToken cancellationToken)
|
public override async Task<SourceManga?> GetMangaAsync(string url, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
HtmlDocument document = await GetHtmlDocumentAsync(url, cancellationToken);
|
HtmlDocument document = await htmlLoader.GetHtmlDocumentAsync(url, cancellationToken);
|
||||||
NatoMangaHtmlDocument node = new(document);
|
NatoMangaHtmlDocument node = new(document);
|
||||||
|
|
||||||
SourceManga manga = new()
|
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),
|
Genres = GetGenres(node.GenresNode),
|
||||||
Chapters = GetChapters(node.ChapterNodes)
|
Chapters = GetChapters(node.ChapterNodes)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,16 +13,17 @@
|
|||||||
<None Remove="Sources\MangaDex\Api\Manga-Chapter-Response.json" />
|
<None Remove="Sources\MangaDex\Api\Manga-Chapter-Response.json" />
|
||||||
<None Remove="Sources\MangaDex\Api\Manga-Cover-Art-Response.json" />
|
<None Remove="Sources\MangaDex\Api\Manga-Cover-Art-Response.json" />
|
||||||
<None Remove="Sources\MangaDex\Api\Manga-Search-Response-2.json" />
|
<None Remove="Sources\MangaDex\Api\Manga-Search-Response-2.json" />
|
||||||
|
<None Remove="Sources\NatoManga\Api\Manga-Chapter-Response.html" />
|
||||||
<None Remove="WebCrawlers\Samples\MangaNato - Please Go Home, Akutsu-San!.htm" />
|
<None Remove="WebCrawlers\Samples\MangaNato - Please Go Home, Akutsu-San!.htm" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="WebCrawlers\NatoManga\SampleMangaPage.html">
|
<EmbeddedResource Include="Sources\NatoManga\Metadata\Manga-Response.html">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</EmbeddedResource>
|
||||||
<Content Include="WebCrawlers\Samples\MangaNato - Please Go Home, Akutsu-San!.htm">
|
<EmbeddedResource Include="Sources\MangaNato\Metadata\Manga-Response.html">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Cover-Art-Response.json" />
|
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Cover-Art-Response.json" />
|
||||||
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Search-Response-2.json" />
|
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Search-Response-2.json" />
|
||||||
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Search-Response.json" />
|
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Search-Response.json" />
|
||||||
|
<EmbeddedResource Include="Sources\NatoManga\Metadata\Manga-Chapter-Response.html" />
|
||||||
<EmbeddedResource Include="Sources\NatoManga\Api\Manga-Search-Response.json" />
|
<EmbeddedResource Include="Sources\NatoManga\Api\Manga-Search-Response.json" />
|
||||||
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Feed-Response.json" />
|
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Feed-Response.json" />
|
||||||
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Response.json" />
|
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Response.json" />
|
||||||
@@ -59,4 +61,9 @@
|
|||||||
<Using Include="Xunit" />
|
<Using Include="Xunit" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="WebCrawlers\NatoManga\" />
|
||||||
|
<Folder Include="WebCrawlers\Samples\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -17,12 +17,16 @@ public class MangaPipelineTests(TestDbContextFactory factory) : IClassFixture<Te
|
|||||||
|
|
||||||
var sourceManga = new SourceManga
|
var sourceManga = new SourceManga
|
||||||
{
|
{
|
||||||
Title = "Fullmetal Alchemist",
|
Title = new()
|
||||||
|
{
|
||||||
|
Name = "Fullmetal Alchemist",
|
||||||
|
Language = Language.English
|
||||||
|
},
|
||||||
AlternateTitles =
|
AlternateTitles =
|
||||||
[
|
[
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Title = "Hagane no Renkinjutsushi",
|
Name = "Hagane no Renkinjutsushi",
|
||||||
Language = Language.Romaji
|
Language = Language.Romaji
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -49,7 +53,10 @@ public class MangaPipelineTests(TestDbContextFactory factory) : IClassFixture<Te
|
|||||||
await pipeline.RunAsync(request);
|
await pipeline.RunAsync(request);
|
||||||
|
|
||||||
context.Mangas.ShouldHaveSingleItem();
|
context.Mangas.ShouldHaveSingleItem();
|
||||||
context.MangaTitles.ShouldHaveSingleItem();
|
context.MangaTitles.Count().ShouldBe(2);
|
||||||
|
context.MangaTitles.Where(mt => 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.Genres.Count().ShouldBe(2);
|
||||||
context.MangaChapters.ShouldHaveSingleItem();
|
context.MangaChapters.ShouldHaveSingleItem();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using MangaReader.Core.HttpService;
|
using MangaReader.Core.Http;
|
||||||
using MangaReader.Core.Sources.MangaDex.Api;
|
using MangaReader.Core.Sources.MangaDex.Api;
|
||||||
using MangaReader.Tests.Utilities;
|
using MangaReader.Tests.Utilities;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
|
|||||||
@@ -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? 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.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.Count.ShouldBe(5);
|
||||||
|
|
||||||
sourceManga.AlternateTitles[0].Title.ShouldBe("オタクに優しいギャルはいない!?");
|
sourceManga.AlternateTitles[0].Name.ShouldBe("オタクに優しいギャルはいない!?");
|
||||||
sourceManga.AlternateTitles[0].Language.ShouldBe(Language.Japanese);
|
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[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[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[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.AlternateTitles[4].Language.ShouldBe(Language.English);
|
||||||
|
|
||||||
sourceManga.Genres.Count.ShouldBe(5);
|
sourceManga.Genres.Count.ShouldBe(5);
|
||||||
|
|||||||
@@ -1,47 +1,35 @@
|
|||||||
using HtmlAgilityPack;
|
using MangaReader.Core.Http;
|
||||||
using MangaReader.Core.Metadata;
|
using MangaReader.Core.Metadata;
|
||||||
using MangaReader.Core.Sources.MangaNato.Metadata;
|
using MangaReader.Core.Sources.MangaNato.Metadata;
|
||||||
|
using MangaReader.Tests.Utilities;
|
||||||
|
using NSubstitute;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using System.Data;
|
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<HtmlDocument> 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]
|
[Fact]
|
||||||
public async Task Get_Manga()
|
public async Task Get_Manga()
|
||||||
{
|
{
|
||||||
var webCrawler = new TestMangaNatoWebCrawler();
|
string mangaHtml = await ReadJsonResourceAsync("Manga-Response.html");
|
||||||
var manga = await webCrawler.GetMangaAsync(mangaNatoSampleFilePath, CancellationToken.None);
|
|
||||||
|
IHttpService httpService = Substitute.For<IHttpService>();
|
||||||
|
|
||||||
|
httpService.GetStringAsync(Arg.Any<string>(), 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.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",
|
"Kaette kudasai! Akutsu-san",
|
||||||
"Yankee Musume",
|
"Yankee Musume",
|
||||||
"ヤンキー娘",
|
"ヤンキー娘",
|
||||||
@@ -62,8 +50,8 @@ public class UnitTest1
|
|||||||
manga.Votes.ShouldBe(15979);
|
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.ShouldStartWith("Ooyama-kun normally doesn’t get involved with Akutsu-san, a delinquent girl in his class");
|
manga.Description?.Name.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.ShouldEndWith("Artist's Pixiv: https://www.pixiv.net/member.php?id=133935");
|
||||||
|
|
||||||
manga.Chapters.Count.ShouldBe(236);
|
manga.Chapters.Count.ShouldBe(236);
|
||||||
|
|
||||||
@@ -79,4 +67,9 @@ public class UnitTest1
|
|||||||
manga.Chapters[235].Views.ShouldBe(232_200);
|
manga.Chapters[235].Views.ShouldBe(232_200);
|
||||||
manga.Chapters[235].UploadDate.ShouldBe(new DateTime(2021, 8, 24, 1, 8, 0));
|
manga.Chapters[235].UploadDate.ShouldBe(new DateTime(2021, 8, 24, 1, 8, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<string> ReadJsonResourceAsync(string resourceName)
|
||||||
|
{
|
||||||
|
return await ResourceHelper.ReadJsonResourceAsync($"MangaReader.Tests.Sources.MangaNato.Metadata.{resourceName}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using MangaReader.Core.HttpService;
|
using MangaReader.Core.Http;
|
||||||
using MangaReader.Core.Sources.NatoManga.Api;
|
using MangaReader.Core.Sources.NatoManga.Api;
|
||||||
using MangaReader.Tests.Utilities;
|
using MangaReader.Tests.Utilities;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
@@ -34,6 +34,9 @@ public class NatoMangaClientTests
|
|||||||
httpService.GetStringAsync(Arg.Any<string>(), CancellationToken.None)
|
httpService.GetStringAsync(Arg.Any<string>(), CancellationToken.None)
|
||||||
.Returns(Task.FromResult(searchResultJson));
|
.Returns(Task.FromResult(searchResultJson));
|
||||||
|
|
||||||
|
httpService.GetStringAsync(Arg.Any<string>(), Arg.Any<IDictionary<string,string>>(), CancellationToken.None)
|
||||||
|
.Returns(Task.FromResult(searchResultJson));
|
||||||
|
|
||||||
NatoMangaClient natoMangaClient = new(httpService);
|
NatoMangaClient natoMangaClient = new(httpService);
|
||||||
NatoMangaSearchResult[] searchResults = await natoMangaClient.SearchAsync("Gal Can't Be Kind", CancellationToken.None);
|
NatoMangaSearchResult[] searchResults = await natoMangaClient.SearchAsync("Gal Can't Be Kind", CancellationToken.None);
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,35 +1,32 @@
|
|||||||
using HtmlAgilityPack;
|
using MangaReader.Core.Http;
|
||||||
|
using MangaReader.Core.Metadata;
|
||||||
using MangaReader.Core.Sources.NatoManga.Metadata;
|
using MangaReader.Core.Sources.NatoManga.Metadata;
|
||||||
|
using MangaReader.Tests.Utilities;
|
||||||
|
using NSubstitute;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
|
|
||||||
namespace MangaReader.Tests.WebCrawlers.NatoManga;
|
namespace MangaReader.Tests.Sources.NatoManga.Metadata;
|
||||||
|
|
||||||
public class NatoMangaWebCrawlerTests
|
public class NatoMangaWebCrawlerTests
|
||||||
{
|
{
|
||||||
class TestNatoMangaWebCrawler : NatoMangaWebCrawler
|
|
||||||
{
|
|
||||||
protected override Task<HtmlDocument> GetHtmlDocumentAsync(string url, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
HtmlWeb web = new()
|
|
||||||
{
|
|
||||||
UsingCacheIfExists = false
|
|
||||||
};
|
|
||||||
|
|
||||||
return Task.FromResult(web.Load(url));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Get_Manga()
|
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();
|
IHttpService httpService = Substitute.For<IHttpService>();
|
||||||
var manga = await webCrawler.GetMangaAsync(sampleFilePath, CancellationToken.None);
|
|
||||||
|
httpService.GetStringAsync(Arg.Any<string>(), 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.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([
|
//manga.AlternateTitles.ShouldBe([
|
||||||
// "Kaette kudasai! Akutsu-san",
|
// "Kaette kudasai! Akutsu-san",
|
||||||
@@ -63,4 +60,9 @@ public class NatoMangaWebCrawlerTests
|
|||||||
//manga.Chapters[235].Views.ShouldBe(232_200);
|
//manga.Chapters[235].Views.ShouldBe(232_200);
|
||||||
//manga.Chapters[235].UploadDate.ShouldBe(new DateTime(2021, 8, 24, 1, 8, 0));
|
//manga.Chapters[235].UploadDate.ShouldBe(new DateTime(2021, 8, 24, 1, 8, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<string> ReadJsonResourceAsync(string resourceName)
|
||||||
|
{
|
||||||
|
return await ResourceHelper.ReadJsonResourceAsync($"MangaReader.Tests.Sources.NatoManga.Metadata.{resourceName}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user