From ea8b4a36ffeea1ce58e5344c5207ccb19777742f Mon Sep 17 00:00:00 2001 From: Brian Bicknell Date: Mon, 26 May 2025 17:16:25 -0400 Subject: [PATCH] Added MangaDex Api. Updated project structure. --- .../Extensions/ServiceCollectionExtensions.cs | 2 +- MangaReader.Core/HttpService/HttpService.cs | 3 +- MangaReader.Core/HttpService/IHttpService.cs | 2 +- .../Metadata/IMangaMetadataProvider.cs | 6 + .../{WebCrawlers => Metadata}/MangaStatus.cs | 2 +- .../MangaWebCrawler.cs | 4 +- .../{WebCrawlers => Metadata}/SourceManga.cs | 2 +- .../SourceMangaChapter.cs | 2 +- MangaReader.Core/Pipeline/IMangaPipeline.cs | 2 +- MangaReader.Core/Pipeline/MangaPipeline.cs | 2 +- .../Search/IMangaSearchCoordinator.cs | 2 +- .../Search/MangaSearchCoordinator.cs | 21 +- .../Search/MangaSearchProviderBase.cs | 6 +- MangaReader.Core/Search/MangaSearchResult.cs | 1 - .../Sources/IMangaSourceComponent.cs | 6 + .../Sources/MangaDex/Api/ArtistAttributes.cs | 6 + .../Sources/MangaDex/Api/ArtistEntity.cs | 6 + .../Sources/MangaDex/Api/AuthorAttributes.cs | 6 + .../Sources/MangaDex/Api/AuthorEntity.cs | 6 + .../Sources/MangaDex/Api/ChapterAttributes.cs | 9 + .../Sources/MangaDex/Api/ChapterEntity.cs | 6 + .../MangaDex/Api/CoverArtAttributes.cs | 12 + .../Sources/MangaDex/Api/CoverArtEntity.cs | 6 + .../Sources/MangaDex/Api/CreatorEntity.cs | 6 + .../Sources/MangaDex/Api/IMangaDexClient.cs | 7 + .../Sources/MangaDex/Api/MangaAttributes.cs | 9 + .../Sources/MangaDex/Api/MangaDexClient.cs | 39 + .../Api/MangaDexCollectionResponse.cs | 6 + .../Sources/MangaDex/Api/MangaDexEntity.cs | 8 + .../MangaDex/Api/MangaDexEntityConverter.cs | 33 + .../MangaDex/Api/MangaDexEntityResponse.cs | 6 + .../Sources/MangaDex/Api/MangaDexResponse.cs | 7 + .../MangaDex/Api/MangaDexResponseConverter.cs | 62 + .../Sources/MangaDex/Api/MangaEntity.cs | 6 + .../Sources/MangaDex/Api/PersonAttributes.cs | 24 + .../MangaDex/Api/ScanlationGroupAttributes.cs | 8 + .../MangaDex/Api/ScanlationGroupEntity.cs | 6 + .../Sources/MangaDex/Api/TagAttributes.cs | 9 + .../Sources/MangaDex/Api/TagEntity.cs | 6 + .../Metadata/MangaDexMetadataProvider.cs | 36 + .../Search}/MangaDexSearchProvider.cs | 7 +- .../MangaDex/Search}/MangaDexSearchResult.cs | 2 +- .../Search}/MangaDexSearchResultData.cs | 2 +- .../MangaDexSearchResultDataAttributes.cs | 2 +- .../MangaDexSearchResultDataRelationship.cs | 2 +- .../Metadata/MangaNatoMangaDocument.cs | 69 + .../Metadata}/MangaNatoWebCrawler.cs | 71 +- .../Metadata}/NatoMangaHtmlDocument.cs | 2 +- .../Metadata}/NatoMangaWebCrawler.cs | 75 +- .../Search}/NatoMangaSearchProvider.cs | 18 +- .../Search}/NatoMangaSearchResult.cs | 2 +- .../WebCrawlers/IMangaWebCrawler.cs | 6 - .../MangaNato/MangaNatoMangaDocument.cs | 61 - MangaReader.Tests/MangaReader.Tests.csproj | 4 + .../Search/MangaDex/MangaDexSearchTests.cs | 2 +- .../NatoManga/NatoMangaWebSearchTests.cs | 4 +- .../MangaDex/MangaDexMetadataTests.cs | 51 + .../MangaDex/MetadataSample-Feed.json | 4165 +++++++++++++++++ .../WebCrawlers/MangaDex/MetadataSample.json | 185 + .../NatoManga/NatoMangaWebCrawlerTests.cs | 2 +- MangaReader.Tests/WebCrawlers/UnitTest1.cs | 4 +- 61 files changed, 4937 insertions(+), 197 deletions(-) create mode 100644 MangaReader.Core/Metadata/IMangaMetadataProvider.cs rename MangaReader.Core/{WebCrawlers => Metadata}/MangaStatus.cs (60%) rename MangaReader.Core/{WebCrawlers => Metadata}/MangaWebCrawler.cs (73%) rename MangaReader.Core/{WebCrawlers => Metadata}/SourceManga.cs (93%) rename MangaReader.Core/{WebCrawlers => Metadata}/SourceMangaChapter.cs (86%) create mode 100644 MangaReader.Core/Sources/IMangaSourceComponent.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/ArtistAttributes.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/ArtistEntity.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/AuthorAttributes.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/AuthorEntity.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/ChapterAttributes.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/ChapterEntity.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/CoverArtAttributes.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/CoverArtEntity.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/CreatorEntity.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/IMangaDexClient.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/MangaAttributes.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/MangaDexClient.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/MangaDexCollectionResponse.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/MangaDexEntity.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/MangaDexEntityConverter.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/MangaDexEntityResponse.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/MangaDexResponse.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/MangaDexResponseConverter.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/MangaEntity.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/PersonAttributes.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/ScanlationGroupAttributes.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/ScanlationGroupEntity.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/TagAttributes.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Api/TagEntity.cs create mode 100644 MangaReader.Core/Sources/MangaDex/Metadata/MangaDexMetadataProvider.cs rename MangaReader.Core/{Search/MangaDex => Sources/MangaDex/Search}/MangaDexSearchProvider.cs (94%) rename MangaReader.Core/{Search/MangaDex => Sources/MangaDex/Search}/MangaDexSearchResult.cs (78%) rename MangaReader.Core/{Search/MangaDex => Sources/MangaDex/Search}/MangaDexSearchResultData.cs (81%) rename MangaReader.Core/{Search/MangaDex => Sources/MangaDex/Search}/MangaDexSearchResultDataAttributes.cs (82%) rename MangaReader.Core/{Search/MangaDex => Sources/MangaDex/Search}/MangaDexSearchResultDataRelationship.cs (79%) create mode 100644 MangaReader.Core/Sources/MangaNato/Metadata/MangaNatoMangaDocument.cs rename MangaReader.Core/{WebCrawlers/MangaNato => Sources/MangaNato/Metadata}/MangaNatoWebCrawler.cs (61%) rename MangaReader.Core/{WebCrawlers/NatoManga => Sources/NatoManga/Metadata}/NatoMangaHtmlDocument.cs (93%) rename MangaReader.Core/{WebCrawlers/NatoManga => Sources/NatoManga/Metadata}/NatoMangaWebCrawler.cs (57%) rename MangaReader.Core/{Search/NatoManga => Sources/NatoManga/Search}/NatoMangaSearchProvider.cs (70%) rename MangaReader.Core/{Search/NatoManga => Sources/NatoManga/Search}/NatoMangaSearchResult.cs (85%) delete mode 100644 MangaReader.Core/WebCrawlers/IMangaWebCrawler.cs delete mode 100644 MangaReader.Core/WebCrawlers/MangaNato/MangaNatoMangaDocument.cs create mode 100644 MangaReader.Tests/WebCrawlers/MangaDex/MangaDexMetadataTests.cs create mode 100644 MangaReader.Tests/WebCrawlers/MangaDex/MetadataSample-Feed.json create mode 100644 MangaReader.Tests/WebCrawlers/MangaDex/MetadataSample.json diff --git a/MangaReader.Core/Extensions/ServiceCollectionExtensions.cs b/MangaReader.Core/Extensions/ServiceCollectionExtensions.cs index e5a41f4..7487800 100644 --- a/MangaReader.Core/Extensions/ServiceCollectionExtensions.cs +++ b/MangaReader.Core/Extensions/ServiceCollectionExtensions.cs @@ -1,6 +1,6 @@ using MangaReader.Core.Search; using MangaReader.Core.Search.MangaDex; -using MangaReader.Core.Search.NatoManga; +using MangaReader.Core.Sources.NatoManga.Search; using Microsoft.Extensions.DependencyInjection.Extensions; using System; using System.Collections.Generic; diff --git a/MangaReader.Core/HttpService/HttpService.cs b/MangaReader.Core/HttpService/HttpService.cs index eb87146..1470dda 100644 --- a/MangaReader.Core/HttpService/HttpService.cs +++ b/MangaReader.Core/HttpService/HttpService.cs @@ -2,5 +2,6 @@ public class HttpService(HttpClient httpClient) : IHttpService { - public Task GetStringAsync(string url) => httpClient.GetStringAsync(url); + public Task GetStringAsync(string url, CancellationToken cancellationToken) + => httpClient.GetStringAsync(url, cancellationToken); } \ No newline at end of file diff --git a/MangaReader.Core/HttpService/IHttpService.cs b/MangaReader.Core/HttpService/IHttpService.cs index 877666e..be23c5d 100644 --- a/MangaReader.Core/HttpService/IHttpService.cs +++ b/MangaReader.Core/HttpService/IHttpService.cs @@ -2,5 +2,5 @@ public interface IHttpService { - Task GetStringAsync(string url); + Task GetStringAsync(string url, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/MangaReader.Core/Metadata/IMangaMetadataProvider.cs b/MangaReader.Core/Metadata/IMangaMetadataProvider.cs new file mode 100644 index 0000000..5fd587e --- /dev/null +++ b/MangaReader.Core/Metadata/IMangaMetadataProvider.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Metadata; + +public interface IMangaMetadataProvider +{ + SourceManga GetManga(string url); +} \ No newline at end of file diff --git a/MangaReader.Core/WebCrawlers/MangaStatus.cs b/MangaReader.Core/Metadata/MangaStatus.cs similarity index 60% rename from MangaReader.Core/WebCrawlers/MangaStatus.cs rename to MangaReader.Core/Metadata/MangaStatus.cs index f4781ac..33f67df 100644 --- a/MangaReader.Core/WebCrawlers/MangaStatus.cs +++ b/MangaReader.Core/Metadata/MangaStatus.cs @@ -1,4 +1,4 @@ -namespace MangaReader.Core.WebCrawlers; +namespace MangaReader.Core.Metadata; public enum MangaStatus { diff --git a/MangaReader.Core/WebCrawlers/MangaWebCrawler.cs b/MangaReader.Core/Metadata/MangaWebCrawler.cs similarity index 73% rename from MangaReader.Core/WebCrawlers/MangaWebCrawler.cs rename to MangaReader.Core/Metadata/MangaWebCrawler.cs index 31c2d78..a09c42d 100644 --- a/MangaReader.Core/WebCrawlers/MangaWebCrawler.cs +++ b/MangaReader.Core/Metadata/MangaWebCrawler.cs @@ -1,8 +1,8 @@ using HtmlAgilityPack; -namespace MangaReader.Core.WebCrawlers; +namespace MangaReader.Core.Metadata; -public abstract class MangaWebCrawler : IMangaWebCrawler +public abstract class MangaWebCrawler : IMangaMetadataProvider { public abstract SourceManga GetManga(string url); diff --git a/MangaReader.Core/WebCrawlers/SourceManga.cs b/MangaReader.Core/Metadata/SourceManga.cs similarity index 93% rename from MangaReader.Core/WebCrawlers/SourceManga.cs rename to MangaReader.Core/Metadata/SourceManga.cs index 896c883..ebc5ef0 100644 --- a/MangaReader.Core/WebCrawlers/SourceManga.cs +++ b/MangaReader.Core/Metadata/SourceManga.cs @@ -1,4 +1,4 @@ -namespace MangaReader.Core.WebCrawlers; +namespace MangaReader.Core.Metadata; public class SourceManga { diff --git a/MangaReader.Core/WebCrawlers/SourceMangaChapter.cs b/MangaReader.Core/Metadata/SourceMangaChapter.cs similarity index 86% rename from MangaReader.Core/WebCrawlers/SourceMangaChapter.cs rename to MangaReader.Core/Metadata/SourceMangaChapter.cs index 8351444..7b8f8db 100644 --- a/MangaReader.Core/WebCrawlers/SourceMangaChapter.cs +++ b/MangaReader.Core/Metadata/SourceMangaChapter.cs @@ -1,4 +1,4 @@ -namespace MangaReader.Core.WebCrawlers; +namespace MangaReader.Core.Metadata; public class SourceMangaChapter { diff --git a/MangaReader.Core/Pipeline/IMangaPipeline.cs b/MangaReader.Core/Pipeline/IMangaPipeline.cs index 789d24c..8d73838 100644 --- a/MangaReader.Core/Pipeline/IMangaPipeline.cs +++ b/MangaReader.Core/Pipeline/IMangaPipeline.cs @@ -1,4 +1,4 @@ -using MangaReader.Core.WebCrawlers; +using MangaReader.Core.Metadata; namespace MangaReader.Core.Pipeline; diff --git a/MangaReader.Core/Pipeline/MangaPipeline.cs b/MangaReader.Core/Pipeline/MangaPipeline.cs index a1144a3..16cac66 100644 --- a/MangaReader.Core/Pipeline/MangaPipeline.cs +++ b/MangaReader.Core/Pipeline/MangaPipeline.cs @@ -1,5 +1,5 @@ using MangaReader.Core.Data; -using MangaReader.Core.WebCrawlers; +using MangaReader.Core.Metadata; using Microsoft.EntityFrameworkCore; using System.Text.RegularExpressions; diff --git a/MangaReader.Core/Search/IMangaSearchCoordinator.cs b/MangaReader.Core/Search/IMangaSearchCoordinator.cs index c478bab..a2d67a9 100644 --- a/MangaReader.Core/Search/IMangaSearchCoordinator.cs +++ b/MangaReader.Core/Search/IMangaSearchCoordinator.cs @@ -2,5 +2,5 @@ public interface IMangaSearchCoordinator { - Task SearchAsync(string keyword, CancellationToken cancellationToken); + Task> SearchAsync(string keyword, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/MangaReader.Core/Search/MangaSearchCoordinator.cs b/MangaReader.Core/Search/MangaSearchCoordinator.cs index 4414f42..f2a41f1 100644 --- a/MangaReader.Core/Search/MangaSearchCoordinator.cs +++ b/MangaReader.Core/Search/MangaSearchCoordinator.cs @@ -1,12 +1,25 @@ -namespace MangaReader.Core.Search; +using MangaReader.Core.Sources; + +namespace MangaReader.Core.Search; public class MangaSearchCoordinator(IEnumerable searchProviders) : IMangaSearchCoordinator { - public async Task SearchAsync(string keyword, CancellationToken cancellationToken) + public async Task> SearchAsync(string keyword, CancellationToken cancellationToken) { - var searchTasks = searchProviders.Select(searchProvider => searchProvider.SearchAsync(keyword, cancellationToken)); + var searchTasks = searchProviders.Select(searchProvider => + GetMangaSearchResultsAsync(searchProvider, keyword, cancellationToken)); + var allResults = await Task.WhenAll(searchTasks); - return [.. allResults.SelectMany(mangaSearchResults => mangaSearchResults)]; + return allResults.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + + private static async Task> GetMangaSearchResultsAsync( + IMangaSearchProvider searchProvider, string keyword, CancellationToken cancellationToken) + { + string sourceName = searchProvider is IMangaSourceComponent sourceComponent ? sourceComponent.SourceId : "Unknown"; + MangaSearchResult[] results = await searchProvider.SearchAsync(keyword, cancellationToken); + + return new KeyValuePair(sourceName, results); } } \ No newline at end of file diff --git a/MangaReader.Core/Search/MangaSearchProviderBase.cs b/MangaReader.Core/Search/MangaSearchProviderBase.cs index 5a25bb8..10a244c 100644 --- a/MangaReader.Core/Search/MangaSearchProviderBase.cs +++ b/MangaReader.Core/Search/MangaSearchProviderBase.cs @@ -12,7 +12,7 @@ public abstract class MangaSearchProviderBase(IHttpService httpService) : IMa public async Task SearchAsync(string keyword, CancellationToken cancellationToken) { - T? searchResult = await GetSearchResultAsync(keyword); + T? searchResult = await GetSearchResultAsync(keyword, cancellationToken); if (searchResult == null) return []; @@ -20,10 +20,10 @@ public abstract class MangaSearchProviderBase(IHttpService httpService) : IMa return GetSearchResult(searchResult); } - private async Task GetSearchResultAsync(string keyword) + private async Task GetSearchResultAsync(string keyword, CancellationToken cancellationToken) { string url = GetSearchUrl(keyword); - string response = await httpService.GetStringAsync(url); + string response = await httpService.GetStringAsync(url, cancellationToken); return JsonSerializer.Deserialize(response, _jsonSerializerOptions); } diff --git a/MangaReader.Core/Search/MangaSearchResult.cs b/MangaReader.Core/Search/MangaSearchResult.cs index a2d7b3d..19b9b15 100644 --- a/MangaReader.Core/Search/MangaSearchResult.cs +++ b/MangaReader.Core/Search/MangaSearchResult.cs @@ -2,7 +2,6 @@ public record MangaSearchResult { - public required string Source { get; init; } public required string Url { get; init; } public required string Title { get; init; } public string? Thumbnail { get; init; } diff --git a/MangaReader.Core/Sources/IMangaSourceComponent.cs b/MangaReader.Core/Sources/IMangaSourceComponent.cs new file mode 100644 index 0000000..a5b3e7e --- /dev/null +++ b/MangaReader.Core/Sources/IMangaSourceComponent.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Sources; + +public interface IMangaSourceComponent +{ + string SourceId { get; } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/ArtistAttributes.cs b/MangaReader.Core/Sources/MangaDex/Api/ArtistAttributes.cs new file mode 100644 index 0000000..ca18d9d --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/ArtistAttributes.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class ArtistAttributes : PersonAttributes +{ + +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/ArtistEntity.cs b/MangaReader.Core/Sources/MangaDex/Api/ArtistEntity.cs new file mode 100644 index 0000000..d19bcd3 --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/ArtistEntity.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class ArtistEntity : MangaDexEntity +{ + public required ArtistAttributes Attributes { get; set; } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/AuthorAttributes.cs b/MangaReader.Core/Sources/MangaDex/Api/AuthorAttributes.cs new file mode 100644 index 0000000..217c2ae --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/AuthorAttributes.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class AuthorAttributes : PersonAttributes +{ + +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/AuthorEntity.cs b/MangaReader.Core/Sources/MangaDex/Api/AuthorEntity.cs new file mode 100644 index 0000000..5231ccd --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/AuthorEntity.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class AuthorEntity : MangaDexEntity +{ + public required AuthorAttributes Attributes { get; set; } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/ChapterAttributes.cs b/MangaReader.Core/Sources/MangaDex/Api/ChapterAttributes.cs new file mode 100644 index 0000000..e4dfa48 --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/ChapterAttributes.cs @@ -0,0 +1,9 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class ChapterAttributes +{ + public string? Chapter { get; set; } + public string? Title { get; set; } + public int Pages { get; set; } + public DateTime PublishAt { get; set; } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/ChapterEntity.cs b/MangaReader.Core/Sources/MangaDex/Api/ChapterEntity.cs new file mode 100644 index 0000000..fc6fbeb --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/ChapterEntity.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class ChapterEntity : MangaDexEntity +{ + public required ChapterAttributes Attributes { get; set; } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/CoverArtAttributes.cs b/MangaReader.Core/Sources/MangaDex/Api/CoverArtAttributes.cs new file mode 100644 index 0000000..43727ca --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/CoverArtAttributes.cs @@ -0,0 +1,12 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class CoverArtAttributes +{ + public string? Description { get; set; } + public string? Volume { get; set; } + public required string FileName { get; set; } + public string? Locale { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public int Version { get; set; } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/CoverArtEntity.cs b/MangaReader.Core/Sources/MangaDex/Api/CoverArtEntity.cs new file mode 100644 index 0000000..051d1ed --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/CoverArtEntity.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class CoverArtEntity : MangaDexEntity +{ + public required CoverArtAttributes Attributes { get; set; } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/CreatorEntity.cs b/MangaReader.Core/Sources/MangaDex/Api/CreatorEntity.cs new file mode 100644 index 0000000..9510fbf --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/CreatorEntity.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class CreatorEntity : MangaDexEntity +{ + +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/IMangaDexClient.cs b/MangaReader.Core/Sources/MangaDex/Api/IMangaDexClient.cs new file mode 100644 index 0000000..b0e8dc2 --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/IMangaDexClient.cs @@ -0,0 +1,7 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public interface IMangaDexClient +{ + Task GetMangaAsync(Guid mangaGuid, CancellationToken cancellationToken); + Task GetFeedAsync(Guid mangaGuid, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/MangaAttributes.cs b/MangaReader.Core/Sources/MangaDex/Api/MangaAttributes.cs new file mode 100644 index 0000000..fa6986c --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/MangaAttributes.cs @@ -0,0 +1,9 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class MangaAttributes +{ + public Dictionary Title { get; set; } = []; + public List> AltTitles { get; set; } = []; + public Dictionary Description { get; set; } = []; + public List Tags { get; set; } = []; +} diff --git a/MangaReader.Core/Sources/MangaDex/Api/MangaDexClient.cs b/MangaReader.Core/Sources/MangaDex/Api/MangaDexClient.cs new file mode 100644 index 0000000..82287ab --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/MangaDexClient.cs @@ -0,0 +1,39 @@ +using MangaReader.Core.HttpService; +using System.Text.Json; + +namespace MangaReader.Core.Sources.MangaDex.Api +{ + public class MangaDexClient(IHttpService httpService) : IMangaDexClient + { + private static readonly JsonSerializerOptions _jsonSerializerOptions; + + static MangaDexClient() + { + _jsonSerializerOptions = new() + { + PropertyNameCaseInsensitive = true + }; + + _jsonSerializerOptions.Converters.Add(new MangaDexResponseConverter()); + _jsonSerializerOptions.Converters.Add(new MangaDexEntityConverter()); + } + + private async Task GetAsync(string url, CancellationToken cancellationToken) + { + string response = await httpService.GetStringAsync(url, cancellationToken); + + return JsonSerializer.Deserialize(response, _jsonSerializerOptions) + ?? new() { Response = "failed", Result = "unknown" }; + } + + public async Task GetMangaAsync(Guid mangaGuid, CancellationToken cancellationToken) + { + return await GetAsync($"https://api.mangadex.org/manga/{mangaGuid}?includes[]=artist&includes[]=author&includes[]=cover_art", cancellationToken); + } + + public async Task GetFeedAsync(Guid mangaGuid, CancellationToken cancellationToken) + { + return await GetAsync($"https://api.mangadex.org/manga/{mangaGuid}/feed?translatedLanguage[]=en&limit=96&includes[]=scanlation_group&includes[]=user&order[volume]=desc&order[chapter]=desc&offset=0&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic", cancellationToken); + } + } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/MangaDexCollectionResponse.cs b/MangaReader.Core/Sources/MangaDex/Api/MangaDexCollectionResponse.cs new file mode 100644 index 0000000..a3e16bf --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/MangaDexCollectionResponse.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class MangaDexCollectionResponse : MangaDexResponse +{ + public List Data { get; set; } = []; +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/MangaDexEntity.cs b/MangaReader.Core/Sources/MangaDex/Api/MangaDexEntity.cs new file mode 100644 index 0000000..0300450 --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/MangaDexEntity.cs @@ -0,0 +1,8 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class MangaDexEntity +{ + public required Guid Id { get; set; } + public required string Type { get; set; } + public List Relationships { get; set; } = []; +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/MangaDexEntityConverter.cs b/MangaReader.Core/Sources/MangaDex/Api/MangaDexEntityConverter.cs new file mode 100644 index 0000000..c86c08f --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/MangaDexEntityConverter.cs @@ -0,0 +1,33 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class MangaDexEntityConverter : JsonConverter +{ + public override MangaDexEntity? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using var doc = JsonDocument.ParseValue(ref reader); + var root = doc.RootElement; + + var type = root.GetProperty("type").GetString(); + + return type switch + { + "manga" => JsonSerializer.Deserialize(root.GetRawText(), options), + "author" => JsonSerializer.Deserialize(root.GetRawText(), options), + "artist" => JsonSerializer.Deserialize(root.GetRawText(), options), + "creator" => JsonSerializer.Deserialize(root.GetRawText(), options), + "tag" => JsonSerializer.Deserialize(root.GetRawText(), options), + "chapter" => JsonSerializer.Deserialize(root.GetRawText(), options), + "scanlation_group" => JsonSerializer.Deserialize(root.GetRawText(), options), + "cover_art" => JsonSerializer.Deserialize(root.GetRawText(), options), + _ => throw new NotSupportedException($"Unknown type '{type}'") + }; + } + + public override void Write(Utf8JsonWriter writer, MangaDexEntity value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, (object)value, options); + } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/MangaDexEntityResponse.cs b/MangaReader.Core/Sources/MangaDex/Api/MangaDexEntityResponse.cs new file mode 100644 index 0000000..d8c6d33 --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/MangaDexEntityResponse.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class MangaDexEntityResponse : MangaDexResponse +{ + public MangaDexEntity? Data { get; set; } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/MangaDexResponse.cs b/MangaReader.Core/Sources/MangaDex/Api/MangaDexResponse.cs new file mode 100644 index 0000000..6a8a442 --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/MangaDexResponse.cs @@ -0,0 +1,7 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class MangaDexResponse +{ + public required string Result { get; set; } + public required string Response { get; set; } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/MangaDexResponseConverter.cs b/MangaReader.Core/Sources/MangaDex/Api/MangaDexResponseConverter.cs new file mode 100644 index 0000000..0ef83bc --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/MangaDexResponseConverter.cs @@ -0,0 +1,62 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class MangaDexResponseConverter : JsonConverter +{ + public override MangaDexResponse? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using JsonDocument doc = JsonDocument.ParseValue(ref reader); + JsonElement root = doc.RootElement; + + string result = root.GetProperty("result").GetString() ?? "fail"; + string response = root.GetProperty("response").GetString() ?? "unknown"; + + JsonElement dataProperty = root.GetProperty("data"); + + if (response == "collection" && dataProperty.ValueKind == JsonValueKind.Array) + { + MangaDexCollectionResponse collectionResponse = new() + { + Result = result, + Response = response, + Data = [] + }; + + foreach (var item in dataProperty.EnumerateArray()) + { + MangaDexEntity? entity = JsonSerializer.Deserialize(item.GetRawText(), options); + + if (entity != null) + collectionResponse.Data.Add(entity); + } + + return collectionResponse; + } + else if (response == "entity" && dataProperty.ValueKind == JsonValueKind.Object) + { + MangaDexEntityResponse entityResponse = new() + { + Result = result, + Response = response, + Data = JsonSerializer.Deserialize(dataProperty.GetRawText(), options) + }; + + return entityResponse; + } + else + { + return new() + { + Result = result, + Response = response + }; + } + } + + public override void Write(Utf8JsonWriter writer, MangaDexResponse value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, (object)value, options); + } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/MangaEntity.cs b/MangaReader.Core/Sources/MangaDex/Api/MangaEntity.cs new file mode 100644 index 0000000..6347cc8 --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/MangaEntity.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class MangaEntity : MangaDexEntity +{ + public required MangaAttributes Attributes { get; set; } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/PersonAttributes.cs b/MangaReader.Core/Sources/MangaDex/Api/PersonAttributes.cs new file mode 100644 index 0000000..f19f6a5 --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/PersonAttributes.cs @@ -0,0 +1,24 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class PersonAttributes +{ + public string? ImageUrl { get; set; } + public Dictionary Biography { get; set; } = []; + public string? Twitter { get; set; } + public string? Pixiv { get; set; } + public string? MelonBook { get; set; } + public string? Fanbox { get; set; } + public string? Booth { get; set; } + public string? Namicomi { get; set; } + public string? NicoVideo { get; set; } + public string? Skeb { get; set; } + public string? Fantia { get; set; } + public string? Tumblr { get; set; } + public string? Youtube { get; set; } + public string? Weibo { get; set; } + public string? Naver { get; set; } + public string? Website { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public int Version { get; set; } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/ScanlationGroupAttributes.cs b/MangaReader.Core/Sources/MangaDex/Api/ScanlationGroupAttributes.cs new file mode 100644 index 0000000..6b9df5a --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/ScanlationGroupAttributes.cs @@ -0,0 +1,8 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class ScanlationGroupAttributes +{ + public string Name { get; set; } = default!; + public string? Website { get; set; } + // etc... +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/ScanlationGroupEntity.cs b/MangaReader.Core/Sources/MangaDex/Api/ScanlationGroupEntity.cs new file mode 100644 index 0000000..79eb5c0 --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/ScanlationGroupEntity.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class ScanlationGroupEntity : MangaDexEntity +{ + public required ScanlationGroupAttributes Attributes { get; set; } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/TagAttributes.cs b/MangaReader.Core/Sources/MangaDex/Api/TagAttributes.cs new file mode 100644 index 0000000..8b7db3d --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/TagAttributes.cs @@ -0,0 +1,9 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class TagAttributes +{ + public Dictionary Name { get; set; } = []; + public Dictionary Description { get; set; } = []; + public required string Group { get; set; } + public int Version { get; set; } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Api/TagEntity.cs b/MangaReader.Core/Sources/MangaDex/Api/TagEntity.cs new file mode 100644 index 0000000..2e1dada --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Api/TagEntity.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Sources.MangaDex.Api; + +public class TagEntity : MangaDexEntity +{ + public required TagAttributes Attributes { get; set; } +} \ No newline at end of file diff --git a/MangaReader.Core/Sources/MangaDex/Metadata/MangaDexMetadataProvider.cs b/MangaReader.Core/Sources/MangaDex/Metadata/MangaDexMetadataProvider.cs new file mode 100644 index 0000000..ee06e09 --- /dev/null +++ b/MangaReader.Core/Sources/MangaDex/Metadata/MangaDexMetadataProvider.cs @@ -0,0 +1,36 @@ +using MangaReader.Core.HttpService; +using MangaReader.Core.Metadata; + +namespace MangaReader.Core.Sources.MangaDex.Metadata; + +//public class MangaDexMetadataProvider(IHttpService httpService) : IMangaMetadataProvider, IMangaSourceComponent +//{ +// public string SourceId => "MangaDex"; + +// public async Task GetManga(string url) +// { +// Guid mangaGuid = GetSourceMangaGuid(url); +// await GetSomething(mangaGuid); + +// throw new NotImplementedException(); +// } + +// private static Guid GetSourceMangaGuid(string url) +// { +// string[] parts = url.Split('/'); + +// if (parts.Length < 5 || Guid.TryParse(parts[4], out Guid mangaGuid) == false) +// { +// throw new Exception("Unable to get guid from MangaDex url: " + url); +// } + +// return mangaGuid; +// } + +// private async Task GetSomething(Guid mangaGuid) +// { +// // https://api.mangadex.org/manga/ee96e2b7-9af2-4864-9656-649f4d3b6fec?includes[]=artist&includes[]=author&includes[]=cover_art + +// await httpService.GetStringAsync($"https://api.mangadex.org/manga/{mangaGuid}/feed?translatedLanguage[]=en&limit=96&includes[]=scanlation_group&includes[]=user&order[volume]=desc&order[chapter]=desc&offset=0&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic"); +// } +//} \ No newline at end of file diff --git a/MangaReader.Core/Search/MangaDex/MangaDexSearchProvider.cs b/MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchProvider.cs similarity index 94% rename from MangaReader.Core/Search/MangaDex/MangaDexSearchProvider.cs rename to MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchProvider.cs index 66681ff..a312610 100644 --- a/MangaReader.Core/Search/MangaDex/MangaDexSearchProvider.cs +++ b/MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchProvider.cs @@ -1,10 +1,12 @@ using MangaReader.Core.HttpService; +using MangaReader.Core.Sources; +using MangaReader.Core.Sources.MangaDex.Search; using System.Text; using System.Text.RegularExpressions; namespace MangaReader.Core.Search.MangaDex; -public partial class MangaDexSearchProvider(IHttpService httpService) : MangaSearchProviderBase(httpService) +public partial class MangaDexSearchProvider(IHttpService httpService) : MangaSearchProviderBase(httpService), IMangaSourceComponent { [GeneratedRegex(@"[^a-z0-9\s-]")] private static partial Regex InvalidSlugCharactersRegex(); @@ -12,6 +14,8 @@ public partial class MangaDexSearchProvider(IHttpService httpService) : MangaSea [GeneratedRegex(@"\s+")] private static partial Regex WhitespaceRegex(); + public string SourceId => "MangaDex"; + protected override string GetSearchUrl(string keyword) { string normalizedKeyword = keyword.ToLowerInvariant().Normalize(NormalizationForm.FormD); @@ -30,7 +34,6 @@ public partial class MangaDexSearchProvider(IHttpService httpService) : MangaSea MangaSearchResult mangaSearchResult = new() { - Source = "MangaDex", Title = title, Url = $"https://mangadex.org/title/{searchResultData.Id}/{slug}", Thumbnail = GetThumbnail(searchResultData) diff --git a/MangaReader.Core/Search/MangaDex/MangaDexSearchResult.cs b/MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchResult.cs similarity index 78% rename from MangaReader.Core/Search/MangaDex/MangaDexSearchResult.cs rename to MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchResult.cs index ed48cdd..db2afa4 100644 --- a/MangaReader.Core/Search/MangaDex/MangaDexSearchResult.cs +++ b/MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchResult.cs @@ -1,4 +1,4 @@ -namespace MangaReader.Core.Search.MangaDex; +namespace MangaReader.Core.Sources.MangaDex.Search; public class MangaDexSearchResult { diff --git a/MangaReader.Core/Search/MangaDex/MangaDexSearchResultData.cs b/MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchResultData.cs similarity index 81% rename from MangaReader.Core/Search/MangaDex/MangaDexSearchResultData.cs rename to MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchResultData.cs index 9de0b0d..b8e8585 100644 --- a/MangaReader.Core/Search/MangaDex/MangaDexSearchResultData.cs +++ b/MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchResultData.cs @@ -1,4 +1,4 @@ -namespace MangaReader.Core.Search.MangaDex; +namespace MangaReader.Core.Sources.MangaDex.Search; public class MangaDexSearchResultData { diff --git a/MangaReader.Core/Search/MangaDex/MangaDexSearchResultDataAttributes.cs b/MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchResultDataAttributes.cs similarity index 82% rename from MangaReader.Core/Search/MangaDex/MangaDexSearchResultDataAttributes.cs rename to MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchResultDataAttributes.cs index 1f68acc..a486129 100644 --- a/MangaReader.Core/Search/MangaDex/MangaDexSearchResultDataAttributes.cs +++ b/MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchResultDataAttributes.cs @@ -1,4 +1,4 @@ -namespace MangaReader.Core.Search.MangaDex; +namespace MangaReader.Core.Sources.MangaDex.Search; public class MangaDexSearchResultDataAttributes { diff --git a/MangaReader.Core/Search/MangaDex/MangaDexSearchResultDataRelationship.cs b/MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchResultDataRelationship.cs similarity index 79% rename from MangaReader.Core/Search/MangaDex/MangaDexSearchResultDataRelationship.cs rename to MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchResultDataRelationship.cs index 819c323..f259d66 100644 --- a/MangaReader.Core/Search/MangaDex/MangaDexSearchResultDataRelationship.cs +++ b/MangaReader.Core/Sources/MangaDex/Search/MangaDexSearchResultDataRelationship.cs @@ -1,4 +1,4 @@ -namespace MangaReader.Core.Search.MangaDex; +namespace MangaReader.Core.Sources.MangaDex.Search; public class MangaDexSearchResultDataRelationship { diff --git a/MangaReader.Core/Sources/MangaNato/Metadata/MangaNatoMangaDocument.cs b/MangaReader.Core/Sources/MangaNato/Metadata/MangaNatoMangaDocument.cs new file mode 100644 index 0000000..80412da --- /dev/null +++ b/MangaReader.Core/Sources/MangaNato/Metadata/MangaNatoMangaDocument.cs @@ -0,0 +1,69 @@ +using HtmlAgilityPack; + +namespace MangaReader.Core.Sources.MangaNato.Metadata; + +public class MangaNatoMangaDocument +{ + public HtmlNode? StoryInfoNode { get; } + public HtmlNode? TitleNode { get; } + public HtmlNode? StoryInfoRightNode { get; } + public HtmlNode? VariationsTableInfo { get; } + public HtmlNodeCollection? VariationsTableValueNodes { get; } + public HtmlNode? AlternateTitlesNode { get; } + public HtmlNode? AuthorsNode { get; } + public HtmlNode? StatusNode { get; } + public HtmlNode? GenresNode { get; } + public HtmlNode? StoryInfoRightExtentNode { get; } + public HtmlNodeCollection? StoryInfoRightExtentValueNodes { get; } + public HtmlNode? UpdateDateNode { get; } + public HtmlNode? ViewsNode { get; } + public HtmlNode? ReviewAggregateNode { get; } + public HtmlNode? RatingNode { get; } + public HtmlNode? AverageRatingNode { get; } + public HtmlNode? BestRatingNode { get; } + public HtmlNode? VotesNode { get; set; } + public HtmlNode? StoryDescriptionNode { get; } + public List StoryDescriptionTextNodes { get; } + public HtmlNode? StoryChapterListNode { get; } + public HtmlNodeCollection? ChapterNodes { get; } + + public MangaNatoMangaDocument(HtmlDocument document) + { + StoryInfoNode = document.DocumentNode.SelectSingleNode(".//div[@class='panel-story-info']"); + TitleNode = StoryInfoNode?.SelectSingleNode(".//h1"); + StoryDescriptionNode = StoryInfoNode?.SelectSingleNode(".//div[@class='panel-story-info-description']"); + StoryDescriptionTextNodes = StoryDescriptionNode?.ChildNodes.Skip(2).Take(StoryDescriptionNode.ChildNodes.Count - 2).ToList() ?? []; + + StoryInfoRightNode = StoryInfoNode?.SelectSingleNode(".//div[@class='story-info-right']"); + + VariationsTableInfo = StoryInfoRightNode?.SelectSingleNode(".//table[@class='variations-tableInfo']"); + VariationsTableValueNodes = VariationsTableInfo?.SelectNodes(".//td[@class='table-value']"); + AlternateTitlesNode = VariationsTableValueNodes?.FirstOrDefault(); + + if (VariationsTableValueNodes != null && VariationsTableValueNodes.Count >= 3) + { + AuthorsNode = VariationsTableValueNodes[1]; + StatusNode = VariationsTableValueNodes[2]; + GenresNode = VariationsTableValueNodes[3]; + } + + StoryInfoRightExtentNode = StoryInfoRightNode?.SelectSingleNode(".//div[@class='story-info-right-extent']"); + StoryInfoRightExtentValueNodes = StoryInfoRightExtentNode?.SelectNodes(".//span[@class='stre-value']"); + + if (StoryInfoRightExtentValueNodes != null && StoryInfoRightExtentValueNodes.Count >= 2) + { + UpdateDateNode = StoryInfoRightExtentValueNodes[0]; + ViewsNode = StoryInfoRightExtentValueNodes[1]; + } + + // v:Review-aggregate + ReviewAggregateNode = StoryInfoRightNode?.SelectSingleNode(".//em[@typeof='v:Review-aggregate']"); + RatingNode = ReviewAggregateNode?.SelectSingleNode(".//em[@typeof='v:Rating']"); + AverageRatingNode = RatingNode?.SelectSingleNode(".//em[@property='v:average']"); + BestRatingNode = RatingNode?.SelectSingleNode(".//em[@property='v:best']"); + VotesNode = ReviewAggregateNode?.SelectSingleNode(".//em[@property='v:votes']"); + + StoryChapterListNode = document.DocumentNode.SelectSingleNode(".//div[@class='panel-story-chapter-list']"); + ChapterNodes = StoryChapterListNode?.SelectNodes(".//li[@class='a-h']"); + } +} \ No newline at end of file diff --git a/MangaReader.Core/WebCrawlers/MangaNato/MangaNatoWebCrawler.cs b/MangaReader.Core/Sources/MangaNato/Metadata/MangaNatoWebCrawler.cs similarity index 61% rename from MangaReader.Core/WebCrawlers/MangaNato/MangaNatoWebCrawler.cs rename to MangaReader.Core/Sources/MangaNato/Metadata/MangaNatoWebCrawler.cs index 479d885..4ee2188 100644 --- a/MangaReader.Core/WebCrawlers/MangaNato/MangaNatoWebCrawler.cs +++ b/MangaReader.Core/Sources/MangaNato/Metadata/MangaNatoWebCrawler.cs @@ -1,8 +1,9 @@ using HtmlAgilityPack; +using MangaReader.Core.Metadata; using System.Text; using System.Web; -namespace MangaReader.Core.WebCrawlers.MangaNato; +namespace MangaReader.Core.Sources.MangaNato.Metadata; public class MangaNatoWebCrawler : MangaWebCrawler { @@ -13,14 +14,14 @@ public class MangaNatoWebCrawler : MangaWebCrawler SourceManga manga = new() { - Title = node.TitleNode.InnerText, + Title = node.TitleNode?.InnerText ?? string.Empty, AlternateTitles = GetAlternateTitles(node.AlternateTitlesNode), Authors = GetAuthors(node.AuthorsNode), Status = GetStatus(node.StatusNode), Genres = GetGenres(node.GenresNode), UpdateDate = GetUpdateDate(node.UpdateDateNode), RatingPercent = GetRatingPercent(node.AverageRatingNode, node.BestRatingNode), - Votes = int.Parse(node.VotesNode.InnerText), + Votes = node.VotesNode != null ? int.Parse(node.VotesNode.InnerText) : 0, Views = GetViews(node.ViewsNode), Description = GetTextFromNodes(node.StoryDescriptionTextNodes), Chapters = GetChapters(node.ChapterNodes) @@ -29,19 +30,25 @@ public class MangaNatoWebCrawler : MangaWebCrawler return manga; } - private static List GetAlternateTitles(HtmlNode node) + private static List GetAlternateTitles(HtmlNode? node) { - return node.InnerText.Split(';').Select(x => x.Trim()).ToList(); + if (node == null) + return []; + + return [.. node.InnerText.Split(';').Select(x => x.Trim())]; } - private static List GetAuthors(HtmlNode node) + private static List GetAuthors(HtmlNode? node) { - return node.InnerText.Split('-').Select(x => x.Trim()).ToList(); + if (node == null) + return []; + + return [.. node.InnerText.Split('-').Select(x => x.Trim())]; } - private static MangaStatus GetStatus(HtmlNode node) + private static MangaStatus GetStatus(HtmlNode? node) { - return node.InnerText switch + return node?.InnerText switch { "Ongoing" => MangaStatus.Ongoing, "Completed" => MangaStatus.Complete, @@ -49,22 +56,31 @@ public class MangaNatoWebCrawler : MangaWebCrawler }; } - private static List GetGenres(HtmlNode node) + private static List GetGenres(HtmlNode? node) { - return node.InnerText.Split('-').Select(x => x.Trim()).ToList(); + if (node == null) + return []; + + return [.. node.InnerText.Split('-').Select(x => x.Trim())]; } - private static DateTime GetUpdateDate(HtmlNode node) + private static DateTime? GetUpdateDate(HtmlNode? node) { - List dateAndTime = node.InnerText.Split('-').Select(x => x.Trim()).ToList(); + if (node == null) + return null; + + List dateAndTime = [.. node.InnerText.Split('-').Select(x => x.Trim())]; DateOnly date = DateOnly.Parse(dateAndTime[0]); TimeOnly time = TimeOnly.Parse(dateAndTime[1]); return date.ToDateTime(time); } - private static long GetViews(HtmlNode node) + private static long GetViews(HtmlNode? node) { + if (node == null) + return 0; + string text = node.InnerText; if (int.TryParse(text, out int number)) @@ -93,31 +109,37 @@ public class MangaNatoWebCrawler : MangaWebCrawler }; } - private static int GetRatingPercent(HtmlNode averageNode, HtmlNode bestNode) + private static int GetRatingPercent(HtmlNode? averageNode, HtmlNode? bestNode) { + if (averageNode == null || bestNode == null) + return 0; + double average = Convert.ToDouble(averageNode.InnerText); double best = Convert.ToDouble(bestNode.InnerText); return (int)Math.Round(average / best * 100); } - private static List GetChapters(HtmlNodeCollection chapterNodes) + private static List GetChapters(HtmlNodeCollection? chapterNodes) { List chapters = []; + if (chapterNodes == null) + return chapters; + foreach (var node in chapterNodes) { - HtmlNode chapterNameNode = node.SelectSingleNode(".//a[contains(@class, 'chapter-name')]"); - HtmlNode chapterViewNode = node.SelectSingleNode(".//span[contains(@class, 'chapter-view')]"); - HtmlNode chapterTimeNode = node.SelectSingleNode(".//span[contains(@class, 'chapter-time')]"); + HtmlNode? chapterNameNode = node.SelectSingleNode(".//a[contains(@class, 'chapter-name')]"); + HtmlNode? chapterViewNode = node.SelectSingleNode(".//span[contains(@class, 'chapter-view')]"); + HtmlNode? chapterTimeNode = node.SelectSingleNode(".//span[contains(@class, 'chapter-time')]"); SourceMangaChapter chapter = new() { Number = GetChapterNumber(chapterNameNode), - Name = chapterNameNode.InnerText, - Url = chapterNameNode.Attributes["href"].Value, + Name = chapterNameNode?.InnerText ?? string.Empty, + Url = chapterNameNode?.Attributes["href"].Value ?? string.Empty, Views = GetViews(chapterViewNode), - UploadDate = DateTime.Parse(chapterTimeNode.Attributes["title"].Value) + UploadDate = chapterTimeNode != null ? DateTime.Parse(chapterTimeNode.Attributes["title"].Value) : null }; chapters.Add(chapter); @@ -126,8 +148,11 @@ public class MangaNatoWebCrawler : MangaWebCrawler return chapters; } - private static float GetChapterNumber(HtmlNode chapterNameNode) + private static float GetChapterNumber(HtmlNode? chapterNameNode) { + if (chapterNameNode == null) + return 0; + string url = chapterNameNode.Attributes["href"].Value; int index = url.IndexOf("/chapter-"); diff --git a/MangaReader.Core/WebCrawlers/NatoManga/NatoMangaHtmlDocument.cs b/MangaReader.Core/Sources/NatoManga/Metadata/NatoMangaHtmlDocument.cs similarity index 93% rename from MangaReader.Core/WebCrawlers/NatoManga/NatoMangaHtmlDocument.cs rename to MangaReader.Core/Sources/NatoManga/Metadata/NatoMangaHtmlDocument.cs index dfdef2f..dee246d 100644 --- a/MangaReader.Core/WebCrawlers/NatoManga/NatoMangaHtmlDocument.cs +++ b/MangaReader.Core/Sources/NatoManga/Metadata/NatoMangaHtmlDocument.cs @@ -1,6 +1,6 @@ using HtmlAgilityPack; -namespace MangaReader.Core.WebCrawlers.NatoManga; +namespace MangaReader.Core.Sources.NatoManga.Metadata; public class NatoMangaHtmlDocument { diff --git a/MangaReader.Core/WebCrawlers/NatoManga/NatoMangaWebCrawler.cs b/MangaReader.Core/Sources/NatoManga/Metadata/NatoMangaWebCrawler.cs similarity index 57% rename from MangaReader.Core/WebCrawlers/NatoManga/NatoMangaWebCrawler.cs rename to MangaReader.Core/Sources/NatoManga/Metadata/NatoMangaWebCrawler.cs index 709c143..03bafb1 100644 --- a/MangaReader.Core/WebCrawlers/NatoManga/NatoMangaWebCrawler.cs +++ b/MangaReader.Core/Sources/NatoManga/Metadata/NatoMangaWebCrawler.cs @@ -1,11 +1,12 @@ using HtmlAgilityPack; -using System.Text; -using System.Web; +using MangaReader.Core.Metadata; -namespace MangaReader.Core.WebCrawlers.NatoManga; +namespace MangaReader.Core.Sources.NatoManga.Metadata; -public class NatoMangaWebCrawler : MangaWebCrawler +public class NatoMangaWebCrawler : MangaWebCrawler, IMangaSourceComponent { + public string SourceId => "NatoManga"; + public override SourceManga GetManga(string url) { HtmlDocument document = GetHtmlDocument(url); @@ -14,47 +15,19 @@ public class NatoMangaWebCrawler : MangaWebCrawler SourceManga manga = new() { Title = node.TitleNode?.InnerText ?? string.Empty, - //AlternateTitles = GetAlternateTitles(node.AlternateTitlesNode), - //Authors = GetAuthors(node.AuthorsNode), - //Status = GetStatus(node.StatusNode), Genres = GetGenres(node.GenresNode), - //UpdateDate = GetUpdateDate(node.UpdateDateNode), - //RatingPercent = GetRatingPercent(node.AverageRatingNode, node.BestRatingNode), - //Votes = int.Parse(node.VotesNode.InnerText), - //Views = GetViews(node.ViewsNode), - //Description = GetTextFromNodes(node.StoryDescriptionTextNodes), Chapters = GetChapters(node.ChapterNodes) }; return manga; } - private static List GetAlternateTitles(HtmlNode node) - { - return node.InnerText.Split(';').Select(x => x.Trim()).ToList(); - } - - private static List GetAuthors(HtmlNode node) - { - return node.InnerText.Split('-').Select(x => x.Trim()).ToList(); - } - - private static MangaStatus GetStatus(HtmlNode node) - { - return node.InnerText switch - { - "Ongoing" => MangaStatus.Ongoing, - "Completed" => MangaStatus.Complete, - _ => MangaStatus.Unknown, - }; - } - private static List GetGenres(HtmlNode? node) { if (node == null) return []; - HtmlNodeCollection genreNodes = node.SelectNodes(".//a"); + HtmlNodeCollection? genreNodes = node.SelectNodes(".//a"); if (genreNodes == null) return []; @@ -62,15 +35,6 @@ public class NatoMangaWebCrawler : MangaWebCrawler return [.. genreNodes.Select(genreNode => genreNode.InnerText.Trim())]; } - private static DateTime GetUpdateDate(HtmlNode node) - { - List dateAndTime = node.InnerText.Split('-').Select(x => x.Trim()).ToList(); - DateOnly date = DateOnly.Parse(dateAndTime[0]); - TimeOnly time = TimeOnly.Parse(dateAndTime[1]); - - return date.ToDateTime(time); - } - private static long GetViews(HtmlNode node) { string text = node.InnerText.Trim(); @@ -108,14 +72,6 @@ public class NatoMangaWebCrawler : MangaWebCrawler }; } - private static int GetRatingPercent(HtmlNode averageNode, HtmlNode bestNode) - { - double average = Convert.ToDouble(averageNode.InnerText); - double best = Convert.ToDouble(bestNode.InnerText); - - return (int)Math.Round(average / best * 100); - } - private static List GetChapters(HtmlNodeCollection? chapterNodes) { List chapters = []; @@ -164,23 +120,4 @@ public class NatoMangaWebCrawler : MangaWebCrawler return float.Parse(chapterNumber); } - - private static string GetTextFromNodes(List nodes) - { - StringBuilder stringBuilder = new(); - - foreach (HtmlNode node in nodes) - { - if (node.Name == "br") - { - stringBuilder.AppendLine(); - } - else - { - stringBuilder.Append(HttpUtility.HtmlDecode(node.InnerText).Replace("\r\n", "").Trim()); - } - } - - return stringBuilder.ToString(); - } } \ No newline at end of file diff --git a/MangaReader.Core/Search/NatoManga/NatoMangaSearchProvider.cs b/MangaReader.Core/Sources/NatoManga/Search/NatoMangaSearchProvider.cs similarity index 70% rename from MangaReader.Core/Search/NatoManga/NatoMangaSearchProvider.cs rename to MangaReader.Core/Sources/NatoManga/Search/NatoMangaSearchProvider.cs index 3a8f05d..c4b8bf2 100644 --- a/MangaReader.Core/Search/NatoManga/NatoMangaSearchProvider.cs +++ b/MangaReader.Core/Sources/NatoManga/Search/NatoMangaSearchProvider.cs @@ -1,12 +1,15 @@ using MangaReader.Core.HttpService; +using MangaReader.Core.Search; using System.Globalization; using System.Text; using System.Text.RegularExpressions; -namespace MangaReader.Core.Search.NatoManga; +namespace MangaReader.Core.Sources.NatoManga.Search; -public class NatoMangaSearchProvider(IHttpService httpService) : MangaSearchProviderBase(httpService) +public partial class NatoMangaSearchProvider(IHttpService httpService) : MangaSearchProviderBase(httpService), IMangaSourceComponent { + public string SourceId => "NatoManga"; + protected override string GetSearchUrl(string keyword) { string formattedSeachWord = GetFormattedSearchWord(keyword); @@ -33,10 +36,10 @@ public class NatoMangaSearchProvider(IHttpService httpService) : MangaSearchProv } // Replace non-alphanumeric characters with underscores - string cleaned = Regex.Replace(sb.ToString(), @"[^a-z0-9]+", "_"); + string cleaned = NonAlphaNumericCharactersRegex().Replace(sb.ToString(), "_"); // Trim and collapse underscores - cleaned = Regex.Replace(cleaned, "_{2,}", "_").Trim('_'); + cleaned = ExtendedUnderscoresRegex().Replace(cleaned, "_").Trim('_'); return cleaned; } @@ -46,7 +49,6 @@ public class NatoMangaSearchProvider(IHttpService httpService) : MangaSearchProv IEnumerable mangaSearchResults = searchResult.Select(searchResult => new MangaSearchResult() { - Source = "NatoManga", Title = searchResult.Name, Thumbnail = searchResult.Thumb, Url = searchResult.Url @@ -54,4 +56,10 @@ public class NatoMangaSearchProvider(IHttpService httpService) : MangaSearchProv return [.. mangaSearchResults]; } + + [GeneratedRegex(@"[^a-z0-9]+")] + private static partial Regex NonAlphaNumericCharactersRegex(); + + [GeneratedRegex("_{2,}")] + private static partial Regex ExtendedUnderscoresRegex(); } \ No newline at end of file diff --git a/MangaReader.Core/Search/NatoManga/NatoMangaSearchResult.cs b/MangaReader.Core/Sources/NatoManga/Search/NatoMangaSearchResult.cs similarity index 85% rename from MangaReader.Core/Search/NatoManga/NatoMangaSearchResult.cs rename to MangaReader.Core/Sources/NatoManga/Search/NatoMangaSearchResult.cs index 427d432..08fc000 100644 --- a/MangaReader.Core/Search/NatoManga/NatoMangaSearchResult.cs +++ b/MangaReader.Core/Sources/NatoManga/Search/NatoMangaSearchResult.cs @@ -1,4 +1,4 @@ -namespace MangaReader.Core.Search.NatoManga; +namespace MangaReader.Core.Sources.NatoManga.Search; public record NatoMangaSearchResult { diff --git a/MangaReader.Core/WebCrawlers/IMangaWebCrawler.cs b/MangaReader.Core/WebCrawlers/IMangaWebCrawler.cs deleted file mode 100644 index 46c00c4..0000000 --- a/MangaReader.Core/WebCrawlers/IMangaWebCrawler.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace MangaReader.Core.WebCrawlers; - -public interface IMangaWebCrawler -{ - SourceManga GetManga(string url); -} \ No newline at end of file diff --git a/MangaReader.Core/WebCrawlers/MangaNato/MangaNatoMangaDocument.cs b/MangaReader.Core/WebCrawlers/MangaNato/MangaNatoMangaDocument.cs deleted file mode 100644 index 146843c..0000000 --- a/MangaReader.Core/WebCrawlers/MangaNato/MangaNatoMangaDocument.cs +++ /dev/null @@ -1,61 +0,0 @@ -using HtmlAgilityPack; - -namespace MangaReader.Core.WebCrawlers.MangaNato; - -public class MangaNatoMangaDocument -{ - public HtmlNode StoryInfoNode { get; } - public HtmlNode TitleNode { get; } - public HtmlNode StoryInfoRightNode { get; } - public HtmlNode VariationsTableInfo { get; } - public HtmlNodeCollection VariationsTableValueNodes { get; } - public HtmlNode AlternateTitlesNode { get; } - public HtmlNode AuthorsNode { get; } - public HtmlNode StatusNode { get; } - public HtmlNode GenresNode { get; } - public HtmlNode StoryInfoRightExtentNode { get; } - public HtmlNodeCollection StoryInfoRightExtentValueNodes { get; } - public HtmlNode UpdateDateNode { get; } - public HtmlNode ViewsNode { get; } - public HtmlNode ReviewAggregateNode { get; } - public HtmlNode RatingNode { get; } - public HtmlNode AverageRatingNode { get; } - public HtmlNode BestRatingNode { get; } - public HtmlNode VotesNode { get; set; } - public HtmlNode StoryDescriptionNode { get; } - public List StoryDescriptionTextNodes { get; } - public HtmlNode StoryChapterListNode { get; } - public HtmlNodeCollection ChapterNodes { get; } - - public MangaNatoMangaDocument(HtmlDocument document) - { - StoryInfoNode = document.DocumentNode.SelectSingleNode(".//div[@class='panel-story-info']"); - TitleNode = StoryInfoNode.SelectSingleNode(".//h1"); - StoryDescriptionNode = StoryInfoNode.SelectSingleNode(".//div[@class='panel-story-info-description']"); - StoryDescriptionTextNodes = StoryDescriptionNode.ChildNodes.Skip(2).Take(StoryDescriptionNode.ChildNodes.Count - 2).ToList(); - - StoryInfoRightNode = StoryInfoNode.SelectSingleNode(".//div[@class='story-info-right']"); - - VariationsTableInfo = StoryInfoRightNode.SelectSingleNode(".//table[@class='variations-tableInfo']"); - VariationsTableValueNodes = VariationsTableInfo.SelectNodes(".//td[@class='table-value']"); - AlternateTitlesNode = VariationsTableValueNodes[0]; - AuthorsNode = VariationsTableValueNodes[1]; - StatusNode = VariationsTableValueNodes[2]; - GenresNode = VariationsTableValueNodes[3]; - - StoryInfoRightExtentNode = StoryInfoRightNode.SelectSingleNode(".//div[@class='story-info-right-extent']"); - StoryInfoRightExtentValueNodes = StoryInfoRightExtentNode.SelectNodes(".//span[@class='stre-value']"); - UpdateDateNode = StoryInfoRightExtentValueNodes[0]; - ViewsNode = StoryInfoRightExtentValueNodes[1]; - - // v:Review-aggregate - ReviewAggregateNode = StoryInfoRightNode.SelectSingleNode(".//em[@typeof='v:Review-aggregate']"); - RatingNode = ReviewAggregateNode.SelectSingleNode(".//em[@typeof='v:Rating']"); - AverageRatingNode = RatingNode.SelectSingleNode(".//em[@property='v:average']"); - BestRatingNode = RatingNode.SelectSingleNode(".//em[@property='v:best']"); - VotesNode = ReviewAggregateNode.SelectSingleNode(".//em[@property='v:votes']"); - - StoryChapterListNode = document.DocumentNode.SelectSingleNode(".//div[@class='panel-story-chapter-list']"); - ChapterNodes = StoryChapterListNode.SelectNodes(".//li[@class='a-h']"); - } -} \ No newline at end of file diff --git a/MangaReader.Tests/MangaReader.Tests.csproj b/MangaReader.Tests/MangaReader.Tests.csproj index e77c1d2..4534f2a 100644 --- a/MangaReader.Tests/MangaReader.Tests.csproj +++ b/MangaReader.Tests/MangaReader.Tests.csproj @@ -11,6 +11,8 @@ + + @@ -27,6 +29,8 @@ + + diff --git a/MangaReader.Tests/Search/MangaDex/MangaDexSearchTests.cs b/MangaReader.Tests/Search/MangaDex/MangaDexSearchTests.cs index 89d52a4..2cb170f 100644 --- a/MangaReader.Tests/Search/MangaDex/MangaDexSearchTests.cs +++ b/MangaReader.Tests/Search/MangaDex/MangaDexSearchTests.cs @@ -36,7 +36,7 @@ public class MangaDexSearchTests IHttpService httpService = Substitute.For(); - httpService.GetStringAsync(Arg.Any()) + httpService.GetStringAsync(Arg.Any(), CancellationToken.None) .Returns(Task.FromResult(searchResultJson)); MangaDexSearchProvider searchProvider = new(httpService); diff --git a/MangaReader.Tests/Search/NatoManga/NatoMangaWebSearchTests.cs b/MangaReader.Tests/Search/NatoManga/NatoMangaWebSearchTests.cs index 07f6a53..ea8ae3c 100644 --- a/MangaReader.Tests/Search/NatoManga/NatoMangaWebSearchTests.cs +++ b/MangaReader.Tests/Search/NatoManga/NatoMangaWebSearchTests.cs @@ -1,6 +1,6 @@ using MangaReader.Core.HttpService; using MangaReader.Core.Search; -using MangaReader.Core.Search.NatoManga; +using MangaReader.Core.Sources.NatoManga.Search; using MangaReader.Tests.Utilities; using NSubstitute; using Shouldly; @@ -31,7 +31,7 @@ public class NatoMangaWebSearchTests IHttpService httpService = Substitute.For(); - httpService.GetStringAsync(Arg.Any()) + httpService.GetStringAsync(Arg.Any(), CancellationToken.None) .Returns(Task.FromResult(searchResultJson)); NatoMangaSearchProvider searchProvider = new(httpService); diff --git a/MangaReader.Tests/WebCrawlers/MangaDex/MangaDexMetadataTests.cs b/MangaReader.Tests/WebCrawlers/MangaDex/MangaDexMetadataTests.cs new file mode 100644 index 0000000..1a70512 --- /dev/null +++ b/MangaReader.Tests/WebCrawlers/MangaDex/MangaDexMetadataTests.cs @@ -0,0 +1,51 @@ +using MangaReader.Core.HttpService; +using MangaReader.Core.Sources.MangaDex.Api; +using MangaReader.Tests.Utilities; +using NSubstitute; +using Shouldly; + +namespace MangaReader.Tests.WebCrawlers.MangaDex; + +public class MangaDexMetadataTests +{ + [Fact] + public async Task Get_Manga_Metadata() + { + string resourceName = "MangaReader.Tests.WebCrawlers.MangaDex.MetadataSample.json"; + string searchResultJson = await ResourceHelper.ReadJsonResourceAsync(resourceName); + + IHttpService httpService = Substitute.For(); + + httpService.GetStringAsync(Arg.Any(), CancellationToken.None) + .Returns(Task.FromResult(searchResultJson)); + + MangaDexClient mangaDexClient = new(httpService); + MangaDexResponse mangaDexResponse = await mangaDexClient.GetMangaAsync(Guid.NewGuid(), CancellationToken.None); + + mangaDexResponse.Response.ShouldBe("entity"); + mangaDexResponse.ShouldBeOfType(); + + MangaDexEntityResponse? mangaDexEntityResponse = mangaDexResponse as MangaDexEntityResponse; + mangaDexEntityResponse.ShouldNotBeNull(); + mangaDexEntityResponse.Data.ShouldNotBeNull(); + mangaDexEntityResponse.Data.ShouldBeOfType(); + + MangaEntity? mangaEntity = mangaDexEntityResponse.Data as MangaEntity; + mangaEntity.ShouldNotBeNull(); + mangaEntity.Attributes.Title.ShouldContainKey("en"); + mangaEntity.Attributes.Title["en"].ShouldBe("Gals Can’t Be Kind to Otaku!?"); + mangaEntity.Attributes.Description.ShouldContainKey("en"); + mangaEntity.Attributes.Description["en"].ShouldBe("Takuya Seo is an otaku who likes \"anime for girls\" and can't say he likes it out loud. One day, he hooks up with two gals from his class, Amane and Ijichi, but it seems that Amane is also an otaku..."); + mangaEntity.Attributes.Tags.Count.ShouldBe(5); + mangaEntity.Attributes.Tags[0].Attributes.Name.ShouldContainKey("en"); + mangaEntity.Attributes.Tags[0].Attributes.Name["en"].ShouldBe("Romance"); + mangaEntity.Attributes.Tags[1].Attributes.Name.ShouldContainKey("en"); + mangaEntity.Attributes.Tags[1].Attributes.Name["en"].ShouldBe("Comedy"); + mangaEntity.Attributes.Tags[2].Attributes.Name.ShouldContainKey("en"); + mangaEntity.Attributes.Tags[2].Attributes.Name["en"].ShouldBe("School Life"); + mangaEntity.Attributes.Tags[3].Attributes.Name.ShouldContainKey("en"); + mangaEntity.Attributes.Tags[3].Attributes.Name["en"].ShouldBe("Slice of Life"); + mangaEntity.Attributes.Tags[4].Attributes.Name.ShouldContainKey("en"); + mangaEntity.Attributes.Tags[4].Attributes.Name["en"].ShouldBe("Gyaru"); + } +} \ No newline at end of file diff --git a/MangaReader.Tests/WebCrawlers/MangaDex/MetadataSample-Feed.json b/MangaReader.Tests/WebCrawlers/MangaDex/MetadataSample-Feed.json new file mode 100644 index 0000000..df6e4c6 --- /dev/null +++ b/MangaReader.Tests/WebCrawlers/MangaDex/MetadataSample-Feed.json @@ -0,0 +1,4165 @@ +{ + "result": "ok", + "response": "collection", + "data": [ + { + "id": "46084762-855c-46dd-a7b6-66e5cd15604d", + "type": "chapter", + "attributes": { + "volume": null, + "chapter": "69", + "title": "Otaku & Gyaru & Playing Couple", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-04-23T20:30:14+00:00", + "readableAt": "2025-04-23T20:30:14+00:00", + "createdAt": "2025-04-23T20:30:14+00:00", + "updatedAt": "2025-04-23T20:30:40+00:00", + "pages": 13, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "7521d1eb-0caf-4c4d-b96a-adc816ada3ec", + "type": "chapter", + "attributes": { + "volume": "9", + "chapter": "68", + "title": "Otaku & Gyaru & A Couple Date", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-04-21T20:27:21+00:00", + "readableAt": "2025-04-21T20:27:21+00:00", + "createdAt": "2025-04-21T20:27:21+00:00", + "updatedAt": "2025-04-21T20:27:45+00:00", + "pages": 14, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "4f6a1d07-0905-4535-af25-67c2ec75c92d", + "type": "chapter", + "attributes": { + "volume": "9", + "chapter": "67", + "title": "Otaku & Gyaru & Gyaru Confession Meeting", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-04-21T20:26:58+00:00", + "readableAt": "2025-04-21T20:26:58+00:00", + "createdAt": "2025-04-21T20:26:57+00:00", + "updatedAt": "2025-04-21T20:27:31+00:00", + "pages": 23, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "0d52b16f-654a-4112-9476-960998545625", + "type": "chapter", + "attributes": { + "volume": "9", + "chapter": "66", + "title": "Otaku & Gyaru & Girl Logic", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-04-21T00:34:36+00:00", + "readableAt": "2025-04-21T00:34:36+00:00", + "createdAt": "2025-04-21T00:34:35+00:00", + "updatedAt": "2025-04-21T00:35:09+00:00", + "pages": 17, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "93f82532-e1ce-416d-a6e2-026821eee2d7", + "type": "chapter", + "attributes": { + "volume": "9", + "chapter": "65", + "title": "Otaku & Gyaru & Dynamic Duo", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-04-20T03:02:41+00:00", + "readableAt": "2025-04-20T03:02:41+00:00", + "createdAt": "2025-04-20T03:02:40+00:00", + "updatedAt": "2025-04-20T03:03:14+00:00", + "pages": 17, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "1b2a41a3-76e1-48b8-a1fe-50646b8c17dd", + "type": "chapter", + "attributes": { + "volume": "9", + "chapter": "64", + "title": "Otaku & Gyaru & A Third Person?!", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-04-20T01:23:03+00:00", + "readableAt": "2025-04-20T01:23:03+00:00", + "createdAt": "2025-04-20T01:23:02+00:00", + "updatedAt": "2025-04-20T01:23:31+00:00", + "pages": 16, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "80b0b096-3bbc-4861-b2d5-afbac55a3b12", + "type": "chapter", + "attributes": { + "volume": "9", + "chapter": "63", + "title": "Otaku & Gyaru & New School Term!!", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-04-19T22:51:15+00:00", + "readableAt": "2025-04-19T22:51:15+00:00", + "createdAt": "2025-04-19T22:51:14+00:00", + "updatedAt": "2025-04-19T22:51:43+00:00", + "pages": 17, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "d068918e-20f2-42a9-9db5-3a63af0fb8c6", + "type": "chapter", + "attributes": { + "volume": "9", + "chapter": "62", + "title": "Otaku & Gyaru & Sunset Magic", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-04-19T22:50:54+00:00", + "readableAt": "2025-04-19T22:50:54+00:00", + "createdAt": "2025-04-19T22:50:53+00:00", + "updatedAt": "2025-04-19T22:51:16+00:00", + "pages": 13, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "82e5c01d-f4b6-4044-87eb-918d6a7cfe20", + "type": "chapter", + "attributes": { + "volume": "9", + "chapter": "61", + "title": "Otaku & Gyaru & Amusement Park Double Date!?", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-04-19T09:07:38+00:00", + "readableAt": "2025-04-19T09:07:38+00:00", + "createdAt": "2025-04-19T09:07:38+00:00", + "updatedAt": "2025-04-19T09:08:10+00:00", + "pages": 17, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "c2165a78-af88-4d7a-9c1d-3a2d26ef43c8", + "type": "chapter", + "attributes": { + "volume": "8", + "chapter": "60", + "title": "Otaku & Gyaru & Cross-filter", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-04-18T22:28:21+00:00", + "readableAt": "2025-04-18T22:28:21+00:00", + "createdAt": "2025-04-18T22:28:21+00:00", + "updatedAt": "2025-04-18T22:28:56+00:00", + "pages": 20, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "02371486-1df8-47b3-8a19-e988700de9cf", + "type": "chapter", + "attributes": { + "volume": "8", + "chapter": "59", + "title": "Otaku & Gyaru & Class Party", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-04-18T22:27:23+00:00", + "readableAt": "2025-04-18T22:27:23+00:00", + "createdAt": "2025-04-18T22:27:22+00:00", + "updatedAt": "2025-04-18T22:27:53+00:00", + "pages": 17, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "bae29c76-6a16-4951-aba4-127b3cb754fc", + "type": "chapter", + "attributes": { + "volume": "8", + "chapter": "58", + "title": "Otaku & Gyaru & Round of Visits", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-04-06T19:46:37+00:00", + "readableAt": "2025-04-06T19:46:37+00:00", + "createdAt": "2025-04-06T19:46:37+00:00", + "updatedAt": "2025-04-06T19:47:09+00:00", + "pages": 17, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "cd71efed-7163-4e0c-b8bc-e982a02d3619", + "type": "chapter", + "attributes": { + "volume": "8", + "chapter": "57", + "title": "Otaku & Gyaru & White Day", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-04-03T11:01:08+00:00", + "readableAt": "2025-04-03T11:01:08+00:00", + "createdAt": "2025-04-03T11:01:07+00:00", + "updatedAt": "2025-04-03T11:01:35+00:00", + "pages": 17, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "91a02b83-a9d1-4234-aeb9-44d86ce480bc", + "type": "chapter", + "attributes": { + "volume": "8", + "chapter": "56", + "title": "Otaku & Gyaru & After Nagoya", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-04-03T06:06:36+00:00", + "readableAt": "2025-04-03T06:06:36+00:00", + "createdAt": "2025-04-03T06:06:29+00:00", + "updatedAt": "2025-04-03T10:59:55+00:00", + "pages": 20, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "1f33cf4e-fff3-401d-afa5-78bca74f6171", + "type": "chapter", + "attributes": { + "volume": "8", + "chapter": "55", + "title": "Otaku & Gyaru & Live Commentary In Person", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-27T14:29:31+00:00", + "readableAt": "2025-02-27T14:29:31+00:00", + "createdAt": "2025-02-27T14:29:31+00:00", + "updatedAt": "2025-02-27T14:33:11+00:00", + "pages": 17, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "bbc1101a-41fb-4c33-aac0-f1cad5dfbbdb", + "type": "chapter", + "attributes": { + "volume": "8", + "chapter": "54", + "title": "Otaku & Gyaru & Late Night After-Party", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-27T14:28:59+00:00", + "readableAt": "2025-02-27T14:28:59+00:00", + "createdAt": "2025-02-27T14:28:58+00:00", + "updatedAt": "2025-02-27T14:29:23+00:00", + "pages": 14, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "252cfc98-6220-4e62-b39a-a2d365eba0e4", + "type": "chapter", + "attributes": { + "volume": "8", + "chapter": "53", + "title": "Otaku & Gyaru & Unexpected Overnight Stay?!", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-25T17:15:56+00:00", + "readableAt": "2025-02-25T17:15:56+00:00", + "createdAt": "2025-02-25T17:15:55+00:00", + "updatedAt": "2025-02-25T17:16:46+00:00", + "pages": 18, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "31af5c06-7564-4aac-9bbc-5b6d90c84197", + "type": "chapter", + "attributes": { + "volume": "7", + "chapter": "52", + "title": "Otaku & Gyaru & Love Chocolate?", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-25T04:10:43+00:00", + "readableAt": "2025-02-25T04:10:43+00:00", + "createdAt": "2025-02-25T04:10:42+00:00", + "updatedAt": "2025-02-25T04:11:23+00:00", + "pages": 22, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "52a7709f-ad5b-4bd9-99cf-fdae16a50dd3", + "type": "chapter", + "attributes": { + "volume": "7", + "chapter": "51", + "title": "Otaku & Gyaru & Valentine's Day", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-25T04:10:21+00:00", + "readableAt": "2025-02-25T04:10:21+00:00", + "createdAt": "2025-02-25T04:10:21+00:00", + "updatedAt": "2025-02-25T04:10:55+00:00", + "pages": 17, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "beaef651-820c-4860-bf93-f484c000c6b6", + "type": "chapter", + "attributes": { + "volume": "7", + "chapter": "50", + "title": "Otaku & Gyaru & Surprise", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-16T06:05:33+00:00", + "readableAt": "2025-02-16T06:05:33+00:00", + "createdAt": "2025-02-16T06:05:32+00:00", + "updatedAt": "2025-02-16T06:06:04+00:00", + "pages": 18, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "91b438fe-9ff3-478b-8bec-9d669f93b712", + "type": "chapter", + "attributes": { + "volume": "7", + "chapter": "49", + "title": "Otaku & Gyaru & Circle of Friends", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-15T02:03:04+00:00", + "readableAt": "2025-02-15T02:03:04+00:00", + "createdAt": "2025-02-15T02:03:03+00:00", + "updatedAt": "2025-02-15T21:13:27+00:00", + "pages": 18, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "e4d1bd23-abdf-4362-93e5-a3eb86ad30f9", + "type": "chapter", + "attributes": { + "volume": "7", + "chapter": "48", + "title": "Otaku & Gyaru & Changing", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-15T02:02:38+00:00", + "readableAt": "2025-02-15T02:02:38+00:00", + "createdAt": "2025-02-15T02:02:38+00:00", + "updatedAt": "2025-02-15T21:14:01+00:00", + "pages": 18, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "2429168a-ccd5-4cec-a3dd-f33f7d16c6a1", + "type": "chapter", + "attributes": { + "volume": "7", + "chapter": "47", + "title": null, + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-06T18:20:01+00:00", + "readableAt": "2025-02-06T18:20:01+00:00", + "createdAt": "2025-02-06T18:20:00+00:00", + "updatedAt": "2025-02-06T20:30:09+00:00", + "pages": 17, + "version": 6 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "2f2a1c45-eed0-4015-91f8-ebdad6a8e638", + "type": "scanlation_group", + "attributes": { + "name": "alt.ver.", + "altNames": [], + "locked": false, + "website": null, + "ircServer": null, + "ircChannel": null, + "discord": null, + "contactEmail": null, + "description": "Functional Group. \n \nTo prevent bugs with the reader. \nTo be used when a group releases two versions of a chapter that both have to stay up.", + "twitter": null, + "mangaUpdates": null, + "focusedLanguages": [ "en" ], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "createdAt": "2021-04-19T21:45:59+00:00", + "updatedAt": "2021-04-19T21:45:59+00:00", + "version": 1 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "21893e9d-885c-44d1-8a89-61c2b1c92e44", + "type": "chapter", + "attributes": { + "volume": "7", + "chapter": "47", + "title": "Otaku & Gyaru & Supporting Faves", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-05T23:59:02+00:00", + "readableAt": "2025-02-05T23:59:02+00:00", + "createdAt": "2025-02-05T23:59:02+00:00", + "updatedAt": "2025-02-06T20:30:52+00:00", + "pages": 17, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "4189e4c3-ca08-46f5-8383-f61956320497", + "type": "chapter", + "attributes": { + "volume": "7", + "chapter": "46", + "title": "Otaku & Gyaru & Year-End Situation", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-02T20:38:48+00:00", + "readableAt": "2025-02-02T20:38:48+00:00", + "createdAt": "2025-02-02T20:38:47+00:00", + "updatedAt": "2025-02-02T20:39:12+00:00", + "pages": 13, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "70a6363c-48f6-4601-b33c-7b23dfd8e0b1", + "type": "chapter", + "attributes": { + "volume": "7", + "chapter": "45", + "title": "Otaku & Gyaru & Feelings of Love", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-02T20:37:52+00:00", + "readableAt": "2025-02-02T20:37:52+00:00", + "createdAt": "2025-02-02T20:37:51+00:00", + "updatedAt": "2025-02-02T20:38:46+00:00", + "pages": 18, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "59f4d060-f73d-4f28-b0cd-792539954e82", + "type": "chapter", + "attributes": { + "volume": "6", + "chapter": "44", + "title": "Otaku & Gyaru & Christmas Eve's Strike", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-02T19:37:32+00:00", + "readableAt": "2025-02-02T19:37:32+00:00", + "createdAt": "2025-02-02T19:37:31+00:00", + "updatedAt": "2025-02-02T19:38:40+00:00", + "pages": 26, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "4f7469a5-2939-4869-b0aa-fb63ea1e23d3", + "type": "chapter", + "attributes": { + "volume": "6", + "chapter": "43", + "title": "Otaku & Gyaru & Christmas Eve", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-02T19:36:53+00:00", + "readableAt": "2025-02-02T19:36:53+00:00", + "createdAt": "2025-02-02T19:36:52+00:00", + "updatedAt": "2025-02-02T19:37:30+00:00", + "pages": 15, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "d96c4f87-2fbf-4ec1-80be-08f72d2a81d8", + "type": "chapter", + "attributes": { + "volume": "6", + "chapter": "42", + "title": "Otaku & Gyaru & Christmas Lights", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-02-02T19:17:20+00:00", + "readableAt": "2025-02-02T19:17:20+00:00", + "createdAt": "2025-02-02T19:17:19+00:00", + "updatedAt": "2025-02-02T19:17:53+00:00", + "pages": 16, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "f50f35f0-e762-4706-a519-7b797e8b1174", + "type": "chapter", + "attributes": { + "volume": "6", + "chapter": "41", + "title": "Otaku & Gyaru & Christmas Present", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-01-16T19:49:39+00:00", + "readableAt": "2025-01-16T19:49:39+00:00", + "createdAt": "2025-01-16T19:49:39+00:00", + "updatedAt": "2025-01-16T19:50:13+00:00", + "pages": 17, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "52fdbbea-12f7-4835-8b17-9d63a69475dc", + "type": "chapter", + "attributes": { + "volume": "6", + "chapter": "40", + "title": "Otaku & Gyaru & Part-time Job", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-01-08T02:46:19+00:00", + "readableAt": "2025-01-08T02:46:19+00:00", + "createdAt": "2025-01-08T02:46:18+00:00", + "updatedAt": "2025-01-08T02:46:47+00:00", + "pages": 17, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "24c39403-17c2-4a38-9590-69814fad8509", + "type": "chapter", + "attributes": { + "volume": "6", + "chapter": "39", + "title": "Otaku & Gyaru & Cake Shop", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-01-05T15:51:39+00:00", + "readableAt": "2025-01-05T15:51:39+00:00", + "createdAt": "2025-01-05T15:51:39+00:00", + "updatedAt": "2025-01-05T15:52:05+00:00", + "pages": 18, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "450cc065-85e3-4d3f-9a11-b6a4bc3a3f93", + "type": "chapter", + "attributes": { + "volume": "6", + "chapter": "38", + "title": "Otaku & Gyaru & Interview Practice", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-01-03T10:03:25+00:00", + "readableAt": "2025-01-03T10:03:25+00:00", + "createdAt": "2025-01-03T10:03:24+00:00", + "updatedAt": "2025-01-03T10:04:14+00:00", + "pages": 29, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "cc8b0c1a-6eb8-4b56-92af-f36191a5f034", + "type": "chapter", + "attributes": { + "volume": "5", + "chapter": "37", + "title": "Otaku & Gyaru & First Names", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-01-02T14:47:52+00:00", + "readableAt": "2025-01-02T14:47:52+00:00", + "createdAt": "2025-01-02T14:47:52+00:00", + "updatedAt": "2025-01-02T14:48:35+00:00", + "pages": 19, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "1307ac23-79a9-409a-aa68-2b4cf28619e6", + "type": "chapter", + "attributes": { + "volume": "5", + "chapter": "36", + "title": "Otaku & Gyaru & Misunderstanding", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2025-01-02T14:47:01+00:00", + "readableAt": "2025-01-02T14:47:01+00:00", + "createdAt": "2025-01-02T14:47:01+00:00", + "updatedAt": "2025-01-02T14:47:35+00:00", + "pages": 20, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "42c3f1b2-7256-4950-906f-d56498eeb79d", + "type": "chapter", + "attributes": { + "volume": "5", + "chapter": "35", + "title": "Otaku & Gyaru & Reminiscent Talk", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-12-04T23:07:32+00:00", + "readableAt": "2024-12-04T23:07:32+00:00", + "createdAt": "2024-12-04T23:07:31+00:00", + "updatedAt": "2024-12-04T23:08:45+00:00", + "pages": 20, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "b7d5190e-261e-4223-8080-a15b21d7906e", + "type": "chapter", + "attributes": { + "volume": "5", + "chapter": "34", + "title": "Otaku & Gyaru & Late Night Love Gossip", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-12-04T18:43:41+00:00", + "readableAt": "2024-12-04T18:43:41+00:00", + "createdAt": "2024-12-04T18:43:40+00:00", + "updatedAt": "2024-12-04T18:45:47+00:00", + "pages": 17, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "9a317dec-dbda-449f-a53d-067b7b99fa4d", + "type": "chapter", + "attributes": { + "volume": "5", + "chapter": "33", + "title": "Otaku & Gyaru & Punishment Game", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-11-07T18:03:23+00:00", + "readableAt": "2024-11-07T18:03:23+00:00", + "createdAt": "2024-11-07T18:03:23+00:00", + "updatedAt": "2024-11-07T19:34:55+00:00", + "pages": 14, + "version": 6 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "92343c0e-b309-47a1-b4aa-018bb6206513", + "type": "chapter", + "attributes": { + "volume": "5", + "chapter": "32", + "title": "Otaku & Gyaru & Takoyaki Party", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-09-29T20:28:42+00:00", + "readableAt": "2024-09-29T20:28:42+00:00", + "createdAt": "2024-09-29T20:28:41+00:00", + "updatedAt": "2024-09-29T20:29:11+00:00", + "pages": 17, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "16337b7c-4f39-4aaa-80b4-00b0b238d899", + "type": "chapter", + "attributes": { + "volume": "5", + "chapter": "31", + "title": "Otaku & Gyaru & Birthday Party", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-09-24T18:55:01+00:00", + "readableAt": "2024-09-24T18:55:01+00:00", + "createdAt": "2024-09-24T18:55:00+00:00", + "updatedAt": "2024-09-24T19:01:29+00:00", + "pages": 18, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "3bc1db69-2eb6-45da-9c47-d0e545c7b205", + "type": "chapter", + "attributes": { + "volume": "5", + "chapter": "30", + "title": "Otaku & Gyaru & Present Choosing", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-08-21T15:19:15+00:00", + "readableAt": "2024-08-21T15:19:15+00:00", + "createdAt": "2024-08-21T15:19:14+00:00", + "updatedAt": "2024-08-21T15:19:42+00:00", + "pages": 21, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "41c2c01a-47ae-4a3c-a700-4cfa9fcad773", + "type": "chapter", + "attributes": { + "volume": "4", + "chapter": "29", + "title": "Otaku & Gyaru & Firework Rumors", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-08-13T20:06:13+00:00", + "readableAt": "2024-08-13T20:06:13+00:00", + "createdAt": "2024-08-13T20:06:13+00:00", + "updatedAt": "2024-08-13T20:06:45+00:00", + "pages": 27, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "01a1b396-aea1-49fc-aae4-8810ef41f32d", + "type": "chapter", + "attributes": { + "volume": "4", + "chapter": "28", + "title": "Otaku & Gyaru & Afterparty", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-08-09T11:08:11+00:00", + "readableAt": "2024-08-09T11:08:11+00:00", + "createdAt": "2024-08-09T11:08:10+00:00", + "updatedAt": "2024-08-09T11:08:25+00:00", + "pages": 14, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "02110ade-d501-40d7-84d6-0ce4ea89182e", + "type": "chapter", + "attributes": { + "volume": "4", + "chapter": "27", + "title": "Otaku & Gyaru & Beauty Contest", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-07-31T18:32:24+00:00", + "readableAt": "2024-07-31T18:32:24+00:00", + "createdAt": "2024-07-31T18:32:24+00:00", + "updatedAt": "2024-07-31T19:37:10+00:00", + "pages": 19, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "663cdc95-a55a-4a39-bd88-3169327eaed2", + "type": "chapter", + "attributes": { + "volume": "4", + "chapter": "26", + "title": "Otaku & Gyaru & School Festival Tour", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-07-22T03:07:38+00:00", + "readableAt": "2024-07-22T03:07:38+00:00", + "createdAt": "2024-07-22T03:07:37+00:00", + "updatedAt": "2024-07-22T03:09:32+00:00", + "pages": 20, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "94e9d554-b655-4fa3-bdcf-9b3ebf33d9c1", + "type": "chapter", + "attributes": { + "volume": "4", + "chapter": "25", + "title": "Otaku & Gyaru & Mini Afterparty", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-06-25T21:13:26+00:00", + "readableAt": "2024-06-25T21:13:26+00:00", + "createdAt": "2024-06-25T21:13:25+00:00", + "updatedAt": "2024-06-25T21:13:48+00:00", + "pages": 20, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "94e8bab8-1e80-4a16-9d01-c7f6d99447c8", + "type": "chapter", + "attributes": { + "volume": "4", + "chapter": "24", + "title": "Otaku & Gyaru & Gal Cosplay", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-06-02T18:33:40+00:00", + "readableAt": "2024-06-02T18:33:40+00:00", + "createdAt": "2024-06-02T18:33:40+00:00", + "updatedAt": "2024-06-02T18:39:03+00:00", + "pages": 19, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "439fcd6e-9be0-4f1c-9d30-21e260b01e58", + "type": "chapter", + "attributes": { + "volume": "4", + "chapter": "23", + "title": "Otaku & Gyaru & Kiramon Cosplays", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-05-21T00:25:42+00:00", + "readableAt": "2024-05-21T00:25:42+00:00", + "createdAt": "2024-05-21T00:25:41+00:00", + "updatedAt": "2024-05-21T00:26:46+00:00", + "pages": 19, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "60598ec2-1ca1-4f13-a9a3-44e8fb5ae3b6", + "type": "chapter", + "attributes": { + "volume": "4", + "chapter": "22", + "title": "Otaku & Gyaru & School Festival Day 1", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-04-22T03:45:55+00:00", + "readableAt": "2024-04-22T03:45:55+00:00", + "createdAt": "2024-04-22T03:45:55+00:00", + "updatedAt": "2024-04-22T03:46:18+00:00", + "pages": 17, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "81225c1d-bfe9-432a-a974-ce14e8e3ab06", + "type": "chapter", + "attributes": { + "volume": "3", + "chapter": "21", + "title": "Otaku & Gyaru & Movie Marathon", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-04-02T19:54:52+00:00", + "readableAt": "2024-04-02T19:54:52+00:00", + "createdAt": "2024-04-02T19:54:51+00:00", + "updatedAt": "2024-04-03T10:11:54+00:00", + "pages": 22, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "80af3b9e-2754-4e09-a080-9b03b24ed3ea", + "type": "chapter", + "attributes": { + "volume": "3", + "chapter": "20", + "title": "Otaku & Gyaru & Sleepover", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-03-17T21:16:47+00:00", + "readableAt": "2024-03-17T21:16:47+00:00", + "createdAt": "2024-03-17T21:16:46+00:00", + "updatedAt": "2024-03-17T21:40:53+00:00", + "pages": 19, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "c8e75162-6fe3-48be-aac2-20995a919c48", + "type": "chapter", + "attributes": { + "volume": "3", + "chapter": "19", + "title": "Otaku & Gyaru & Secret Project", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-03-07T22:08:36+00:00", + "readableAt": "2024-03-07T22:08:36+00:00", + "createdAt": "2024-03-07T22:08:35+00:00", + "updatedAt": "2024-03-07T22:16:24+00:00", + "pages": 19, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "3d4a759d-b7f7-454d-8d45-0bbe22109c38", + "type": "chapter", + "attributes": { + "volume": "3", + "chapter": "18", + "title": "Otaku & Gyaru & School Festival Prep", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-03-03T16:27:12+00:00", + "readableAt": "2024-03-03T16:27:12+00:00", + "createdAt": "2024-03-03T16:25:56+00:00", + "updatedAt": "2024-03-03T16:27:55+00:00", + "pages": 19, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "8cbda35b-35de-4824-9294-2e4d5d83a543", + "type": "chapter", + "attributes": { + "volume": "3", + "chapter": "17", + "title": "Otaku & Gyaru & School Festival", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-02-27T19:29:12+00:00", + "readableAt": "2024-02-27T19:29:12+00:00", + "createdAt": "2024-02-27T19:29:12+00:00", + "updatedAt": "2024-02-27T19:30:01+00:00", + "pages": 19, + "version": 3 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "92407088-b946-4909-9abf-720bb5715716", + "type": "chapter", + "attributes": { + "volume": "3", + "chapter": "16", + "title": "Otaku & Gyaru & Seat Change, New World", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-02-19T10:15:04+00:00", + "readableAt": "2024-02-19T10:15:04+00:00", + "createdAt": "2024-02-19T10:15:03+00:00", + "updatedAt": "2024-02-21T13:38:53+00:00", + "pages": 19, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "442741c8-afa5-47b0-b416-cabdd191fd05", + "type": "chapter", + "attributes": { + "volume": "3", + "chapter": "15", + "title": "Otaku & Gyaru & Short-Term Job", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-01-31T05:28:16+00:00", + "readableAt": "2024-01-31T05:28:16+00:00", + "createdAt": "2024-01-31T05:28:16+00:00", + "updatedAt": "2024-01-31T20:47:20+00:00", + "pages": 19, + "version": 4 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "a2831e92-858d-40bc-9190-5e7ef87870df", + "type": "user", + "attributes": { + "username": "PreStoneX", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_MEMBER", "ROLE_SUPPORTER", "ROLE_POWER_UPLOADER" ], + "version": 19893 + } + } + ] + }, + { + "id": "ac19c206-4ad9-4945-bf5b-3a3213501357", + "type": "chapter", + "attributes": { + "volume": "3", + "chapter": "14", + "title": "Otaku & Gyaru & Little Ones", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2024-01-18T15:39:05+00:00", + "readableAt": "2024-01-18T15:39:05+00:00", + "createdAt": "2024-01-18T15:39:04+00:00", + "updatedAt": "2024-01-20T14:07:52+00:00", + "pages": 17, + "version": 5 + }, + "relationships": [ + { + "id": "2c6c02a1-1983-4444-84a8-c3da32661c39", + "type": "scanlation_group", + "attributes": { + "name": "StoneScape", + "altNames": [], + "locked": false, + "website": "https:\/\/stonescape.xyz\/", + "ircServer": null, + "ircChannel": null, + "discord": "stonescape", + "contactEmail": "ocuharom@gmail.com", + "description": "Hobbyist Fantranslation Group!", + "twitter": null, + "mangaUpdates": "https:\/\/www.mangaupdates.com\/group\/zt7x4q1", + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "exLicensed": false, + "createdAt": "2023-10-19T17:01:37+00:00", + "updatedAt": "2025-05-22T17:22:11+00:00", + "version": 8 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "863ecb60-bfff-40a4-a6b4-a5ca7e9ad685", + "type": "user", + "attributes": { + "username": "Randolol", + "roles": [ "ROLE_USER" ], + "version": 1 + } + } + ] + }, + { + "id": "b90c9558-35d6-45cc-80ed-39085740aef2", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "13", + "title": "Otaku & Gyaru's Summer Festival ", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-12-01T17:41:53+00:00", + "readableAt": "2023-12-01T17:41:53+00:00", + "createdAt": "2023-12-01T17:41:48+00:00", + "updatedAt": "2023-12-01T17:42:39+00:00", + "pages": 30, + "version": 3 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "1694a0e4-1df3-4d09-b788-e019555b2be8", + "type": "user", + "attributes": { + "username": "nyanpasu47", + "roles": [ "ROLE_USER" ], + "version": 5 + } + } + ] + }, + { + "id": "afb4ee99-4174-470d-86eb-7b78593ac8e9", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "12", + "title": "Otaku & Gyaru & BBQ", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-09-26T21:24:06+00:00", + "readableAt": "2023-09-26T21:24:06+00:00", + "createdAt": "2023-09-26T21:24:05+00:00", + "updatedAt": "2023-09-26T21:24:47+00:00", + "pages": 31, + "version": 3 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "1694a0e4-1df3-4d09-b788-e019555b2be8", + "type": "user", + "attributes": { + "username": "nyanpasu47", + "roles": [ "ROLE_USER" ], + "version": 5 + } + } + ] + }, + { + "id": "b8b2147a-500d-4488-bd08-8c83de60e04e", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "11", + "title": "Otaku & Gyaru at Karaoke", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-09-20T18:15:11+00:00", + "readableAt": "2023-09-20T18:15:11+00:00", + "createdAt": "2023-09-20T18:15:09+00:00", + "updatedAt": "2023-09-20T18:15:56+00:00", + "pages": 31, + "version": 3 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "1694a0e4-1df3-4d09-b788-e019555b2be8", + "type": "user", + "attributes": { + "username": "nyanpasu47", + "roles": [ "ROLE_USER" ], + "version": 5 + } + } + ] + }, + { + "id": "03d52be9-04f4-4543-9f55-2b0d5eeed9d7", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "10.6", + "title": null, + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-07-12T22:45:44+00:00", + "readableAt": "2023-07-12T22:45:44+00:00", + "createdAt": "2023-07-12T22:45:43+00:00", + "updatedAt": "2023-07-12T22:45:54+00:00", + "pages": 4, + "version": 3 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "1694a0e4-1df3-4d09-b788-e019555b2be8", + "type": "user", + "attributes": { + "username": "nyanpasu47", + "roles": [ "ROLE_USER" ], + "version": 5 + } + } + ] + }, + { + "id": "741b00dd-f3c8-47a4-b388-eb9ae5207f9b", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "10.5", + "title": null, + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-07-05T18:47:42+00:00", + "readableAt": "2023-07-05T18:47:42+00:00", + "createdAt": "2023-07-05T18:47:42+00:00", + "updatedAt": "2023-07-05T18:47:50+00:00", + "pages": 4, + "version": 3 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "1694a0e4-1df3-4d09-b788-e019555b2be8", + "type": "user", + "attributes": { + "username": "nyanpasu47", + "roles": [ "ROLE_USER" ], + "version": 5 + } + } + ] + }, + { + "id": "e9172c7b-906c-4e54-b933-904500d90ed2", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "10.4", + "title": null, + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-07-04T19:31:56+00:00", + "readableAt": "2023-07-04T19:31:56+00:00", + "createdAt": "2023-07-04T19:31:56+00:00", + "updatedAt": "2023-07-04T21:07:03+00:00", + "pages": 4, + "version": 4 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "1694a0e4-1df3-4d09-b788-e019555b2be8", + "type": "user", + "attributes": { + "username": "nyanpasu47", + "roles": [ "ROLE_USER" ], + "version": 5 + } + } + ] + }, + { + "id": "f6453d7a-e73a-47d3-97a3-b5c450b4c267", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "10.3", + "title": null, + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-07-03T14:21:13+00:00", + "readableAt": "2023-07-03T14:21:13+00:00", + "createdAt": "2023-07-03T14:21:12+00:00", + "updatedAt": "2023-07-03T14:21:25+00:00", + "pages": 4, + "version": 3 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "1694a0e4-1df3-4d09-b788-e019555b2be8", + "type": "user", + "attributes": { + "username": "nyanpasu47", + "roles": [ "ROLE_USER" ], + "version": 5 + } + } + ] + }, + { + "id": "d2dec3e7-dc69-4977-82b6-981c74e7f2c0", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "10.2", + "title": null, + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-07-03T07:43:45+00:00", + "readableAt": "2023-07-03T07:43:45+00:00", + "createdAt": "2023-07-03T07:43:44+00:00", + "updatedAt": "2023-07-03T07:43:52+00:00", + "pages": 4, + "version": 3 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "1694a0e4-1df3-4d09-b788-e019555b2be8", + "type": "user", + "attributes": { + "username": "nyanpasu47", + "roles": [ "ROLE_USER" ], + "version": 5 + } + } + ] + }, + { + "id": "f518fa71-28a1-4ea1-901b-79e80a42b303", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "10.1", + "title": "Otaku & Gyaru's Tournament ", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-07-01T23:15:09+00:00", + "readableAt": "2023-07-01T23:15:09+00:00", + "createdAt": "2023-07-01T23:15:09+00:00", + "updatedAt": "2023-07-01T23:15:18+00:00", + "pages": 5, + "version": 3 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "1694a0e4-1df3-4d09-b788-e019555b2be8", + "type": "user", + "attributes": { + "username": "nyanpasu47", + "roles": [ "ROLE_USER" ], + "version": 5 + } + } + ] + }, + { + "id": "acf61a14-c6c2-4920-b84a-5e97b76462fd", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "9.4", + "title": null, + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-06-27T19:46:56+00:00", + "readableAt": "2023-06-27T19:46:56+00:00", + "createdAt": "2023-06-27T19:46:55+00:00", + "updatedAt": "2023-06-27T19:47:05+00:00", + "pages": 4, + "version": 3 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "1694a0e4-1df3-4d09-b788-e019555b2be8", + "type": "user", + "attributes": { + "username": "nyanpasu47", + "roles": [ "ROLE_USER" ], + "version": 5 + } + } + ] + }, + { + "id": "f53e2d95-5eb4-4186-85ea-b86cedc22628", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "9.3", + "title": null, + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-05-31T10:33:21+00:00", + "readableAt": "2023-05-31T10:33:21+00:00", + "createdAt": "2023-05-31T10:33:20+00:00", + "updatedAt": "2023-05-31T10:33:29+00:00", + "pages": 5, + "version": 3 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "1694a0e4-1df3-4d09-b788-e019555b2be8", + "type": "user", + "attributes": { + "username": "nyanpasu47", + "roles": [ "ROLE_USER" ], + "version": 5 + } + } + ] + }, + { + "id": "0fc965a0-7042-4c4a-b760-dd54641da0f0", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "9.2", + "title": null, + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-05-30T06:15:32+00:00", + "readableAt": "2023-05-30T06:15:32+00:00", + "createdAt": "2023-05-30T06:15:32+00:00", + "updatedAt": "2023-05-30T06:15:44+00:00", + "pages": 8, + "version": 3 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "1694a0e4-1df3-4d09-b788-e019555b2be8", + "type": "user", + "attributes": { + "username": "nyanpasu47", + "roles": [ "ROLE_USER" ], + "version": 5 + } + } + ] + }, + { + "id": "a12a0d98-b561-4fff-a30e-f9672e1d899b", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "9.1", + "title": "Otaku & Gyaru & After School Date", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-05-30T06:53:08+00:00", + "readableAt": "2023-05-30T06:53:08+00:00", + "createdAt": "2023-05-30T06:53:08+00:00", + "updatedAt": "2023-05-30T06:53:17+00:00", + "pages": 5, + "version": 3 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "1694a0e4-1df3-4d09-b788-e019555b2be8", + "type": "user", + "attributes": { + "username": "nyanpasu47", + "roles": [ "ROLE_USER" ], + "version": 5 + } + } + ] + }, + { + "id": "467f9355-4597-4940-bac1-310c0ebdae9b", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "8.2", + "title": null, + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-05-25T17:03:29+00:00", + "readableAt": "2023-05-25T17:03:29+00:00", + "createdAt": "2023-05-24T13:37:22+00:00", + "updatedAt": "2023-05-30T06:31:07+00:00", + "pages": 15, + "version": 5 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "1694a0e4-1df3-4d09-b788-e019555b2be8", + "type": "user", + "attributes": { + "username": "nyanpasu47", + "roles": [ "ROLE_USER" ], + "version": 5 + } + } + ] + }, + { + "id": "b5206e9b-6e3e-4ef0-aa62-381fd0ff75a5", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "8.1", + "title": "Otaku & Gyaru & Protegee", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-09-01T17:15:48+00:00", + "readableAt": "2023-09-01T17:15:48+00:00", + "createdAt": "2023-09-01T17:15:48+00:00", + "updatedAt": "2024-07-25T18:18:18+00:00", + "pages": 16, + "version": 4 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "c7f8bde1-e85a-4fb0-994a-ea0f6c56f2b0", + "type": "user", + "attributes": { + "username": "NotAScanlator", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_POWER_UPLOADER", "ROLE_USER" ], + "version": 3575 + } + } + ] + }, + { + "id": "04a0d673-4c7a-41ee-b1ef-e8d95297472c", + "type": "chapter", + "attributes": { + "volume": "2", + "chapter": "8.1", + "title": "Otaku & Gyaru & Protegee", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-05-21T17:58:43+00:00", + "readableAt": "2023-05-21T17:58:43+00:00", + "createdAt": "2023-05-20T23:12:22+00:00", + "updatedAt": "2023-05-31T16:38:09+00:00", + "pages": 16, + "version": 7 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "6b9845b2-c7d5-4fb9-993d-c5670833d7c2", + "type": "user", + "attributes": { + "username": "gCt17", + "roles": [ "ROLE_USER" ], + "version": 82 + } + } + ] + }, + { + "id": "8a55519b-f431-4cab-b695-8413a00c1faa", + "type": "chapter", + "attributes": { + "volume": "1", + "chapter": "7", + "title": "Otaku & Gyaru & Test", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-09-01T11:12:25+00:00", + "readableAt": "2023-09-01T11:12:25+00:00", + "createdAt": "2023-09-01T11:12:25+00:00", + "updatedAt": "2024-07-26T00:24:13+00:00", + "pages": 27, + "version": 4 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "c7f8bde1-e85a-4fb0-994a-ea0f6c56f2b0", + "type": "user", + "attributes": { + "username": "NotAScanlator", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_POWER_UPLOADER", "ROLE_USER" ], + "version": 3575 + } + } + ] + }, + { + "id": "7ed994eb-2e00-4e1e-b8e9-87127d3dcbf5", + "type": "chapter", + "attributes": { + "volume": "1", + "chapter": "7", + "title": "Otaku & Gyaru & Test", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-05-21T17:58:42+00:00", + "readableAt": "2023-05-21T17:58:42+00:00", + "createdAt": "2023-05-20T23:03:34+00:00", + "updatedAt": "2023-05-21T17:58:42+00:00", + "pages": 27, + "version": 3 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "6b9845b2-c7d5-4fb9-993d-c5670833d7c2", + "type": "user", + "attributes": { + "username": "gCt17", + "roles": [ "ROLE_USER" ], + "version": 82 + } + } + ] + }, + { + "id": "72af33f3-6bfb-447f-beec-c215f802d09f", + "type": "chapter", + "attributes": { + "volume": "1", + "chapter": "6", + "title": "Otaku & Gyaru Casual Clothes Date", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-04-04T08:46:22+00:00", + "readableAt": "2023-04-04T08:46:22+00:00", + "createdAt": "2023-04-04T08:46:21+00:00", + "updatedAt": "2023-04-04T08:47:19+00:00", + "pages": 23, + "version": 3 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "c7f8bde1-e85a-4fb0-994a-ea0f6c56f2b0", + "type": "user", + "attributes": { + "username": "NotAScanlator", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_POWER_UPLOADER", "ROLE_USER" ], + "version": 3575 + } + } + ] + }, + { + "id": "b4990548-a55a-4c6e-9a21-942cdeec5d40", + "type": "chapter", + "attributes": { + "volume": "1", + "chapter": "5", + "title": "Otaku & Gyaru's Contact", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-03-19T05:17:23+00:00", + "readableAt": "2023-03-19T05:17:23+00:00", + "createdAt": "2023-03-19T05:17:22+00:00", + "updatedAt": "2023-03-19T05:43:12+00:00", + "pages": 23, + "version": 5 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "c7f8bde1-e85a-4fb0-994a-ea0f6c56f2b0", + "type": "user", + "attributes": { + "username": "NotAScanlator", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_POWER_UPLOADER", "ROLE_USER" ], + "version": 3575 + } + } + ] + }, + { + "id": "d59cee63-2b46-4e8f-a5c0-6d533e087e2a", + "type": "chapter", + "attributes": { + "volume": "1", + "chapter": "4", + "title": "Otaku & Gyaru & Housecall", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-03-19T05:14:32+00:00", + "readableAt": "2023-03-19T05:14:32+00:00", + "createdAt": "2023-03-19T05:14:31+00:00", + "updatedAt": "2023-03-19T05:43:05+00:00", + "pages": 23, + "version": 4 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "c7f8bde1-e85a-4fb0-994a-ea0f6c56f2b0", + "type": "user", + "attributes": { + "username": "NotAScanlator", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_POWER_UPLOADER", "ROLE_USER" ], + "version": 3575 + } + } + ] + }, + { + "id": "afc9f261-55ec-4e23-a399-78f25a552b61", + "type": "chapter", + "attributes": { + "volume": "1", + "chapter": "3", + "title": "Otaku & Gyaru & Off-Campus Life", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-03-19T05:13:23+00:00", + "readableAt": "2023-03-19T05:13:23+00:00", + "createdAt": "2023-03-19T05:13:22+00:00", + "updatedAt": "2023-03-19T05:42:58+00:00", + "pages": 23, + "version": 4 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "c7f8bde1-e85a-4fb0-994a-ea0f6c56f2b0", + "type": "user", + "attributes": { + "username": "NotAScanlator", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_POWER_UPLOADER", "ROLE_USER" ], + "version": 3575 + } + } + ] + }, + { + "id": "5dc8f35e-281c-4978-8680-6abc1725fba7", + "type": "chapter", + "attributes": { + "volume": "1", + "chapter": "2", + "title": "Otaku & Gyaru & Limited Ed. Figure", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-03-19T05:12:20+00:00", + "readableAt": "2023-03-19T05:12:20+00:00", + "createdAt": "2023-03-19T05:12:19+00:00", + "updatedAt": "2023-03-19T05:42:46+00:00", + "pages": 22, + "version": 4 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "c7f8bde1-e85a-4fb0-994a-ea0f6c56f2b0", + "type": "user", + "attributes": { + "username": "NotAScanlator", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_POWER_UPLOADER", "ROLE_USER" ], + "version": 3575 + } + } + ] + }, + { + "id": "4dc9bbac-2087-41b0-8fd8-34561a77a7d8", + "type": "chapter", + "attributes": { + "volume": "1", + "chapter": "1", + "title": "Otaku & Gyaru & Kiramon", + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2023-03-19T05:11:01+00:00", + "readableAt": "2023-03-19T05:11:01+00:00", + "createdAt": "2023-03-19T05:11:01+00:00", + "updatedAt": "2023-03-19T05:42:38+00:00", + "pages": 26, + "version": 4 + }, + "relationships": [ + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "c7f8bde1-e85a-4fb0-994a-ea0f6c56f2b0", + "type": "user", + "attributes": { + "username": "NotAScanlator", + "roles": [ "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER", "ROLE_POWER_UPLOADER", "ROLE_USER" ], + "version": 3575 + } + } + ] + }, + { + "id": "0c2dccc8-95e8-4bc1-b7ef-7f06dc8c162f", + "type": "chapter", + "attributes": { + "volume": "1", + "chapter": "1", + "title": null, + "translatedLanguage": "en", + "externalUrl": null, + "publishAt": "2022-03-22T19:10:32+00:00", + "readableAt": "2022-03-22T19:10:32+00:00", + "createdAt": "2022-03-22T19:10:32+00:00", + "updatedAt": "2023-04-06T13:39:52+00:00", + "pages": 11, + "version": 7 + }, + "relationships": [ + { + "id": "3ba82821-7866-4f8b-80b4-ce2424e346d7", + "type": "scanlation_group", + "attributes": { + "name": "Synergy", + "altNames": [], + "locked": true, + "website": null, + "ircServer": null, + "ircChannel": null, + "discord": null, + "contactEmail": null, + "description": "Thanks!", + "twitter": null, + "mangaUpdates": null, + "focusedLanguages": [], + "official": false, + "verified": false, + "inactive": false, + "publishDelay": null, + "createdAt": "2021-09-09T23:03:21+00:00", + "updatedAt": "2022-09-11T20:06:36+00:00", + "version": 4 + } + }, + { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga" + }, + { + "id": "0f11758a-e5de-4f4b-8552-5aef28d89b36", + "type": "user", + "attributes": { + "username": "Order&Chaos", + "roles": [ "ROLE_USER", "ROLE_GROUP_LEADER", "ROLE_GROUP_MEMBER" ], + "version": 3 + } + } + ] + } + ], + "limit": 96, + "offset": 0, + "total": 82 +} \ No newline at end of file diff --git a/MangaReader.Tests/WebCrawlers/MangaDex/MetadataSample.json b/MangaReader.Tests/WebCrawlers/MangaDex/MetadataSample.json new file mode 100644 index 0000000..f203a11 --- /dev/null +++ b/MangaReader.Tests/WebCrawlers/MangaDex/MetadataSample.json @@ -0,0 +1,185 @@ +{ + "result": "ok", + "response": "entity", + "data": { + "id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec", + "type": "manga", + "attributes": { + "title": { "en": "Gals Can\u2019t Be Kind to Otaku!?" }, + "altTitles": [ + { "ja": "\u30aa\u30bf\u30af\u306b\u512a\u3057\u3044\u30ae\u30e3\u30eb\u306f\u3044\u306a\u3044\uff01\uff1f" }, + { "ja-ro": "Otaku ni Yasashii Gal wa Inai!?" }, + { "ja-ro": "Otaku ni Yasashii Gyaru ha Inai!?" }, + { "en": "Gal Can\u0027t Be Kind to Otaku!?" }, + { "en": "Gals Can\u0027t Be Kind To A Geek!?" }, + { "zh": "\u6ca1\u6709\u8fa3\u59b9\u4f1a\u5bf9\u963f\u5b85\u6e29\u67d4!?" }, + { "pt-br": "Gals N\u00e3o Podem ser Gentis com Otakus!?" }, + { "es-la": "\u00bf\u00a1Las Gals no pueden ser amables con los Otakus!?" }, + { "vi": "Gyaru kh\u00f4ng th\u1ec3 t\u1eed t\u1ebf v\u1edbi Otaku \u01b0?" } + ], + "description": { + "en": "Takuya Seo is an otaku who likes \u0022anime for girls\u0022 and can\u0027t say he likes it out loud. One day, he hooks up with two gals from his class, Amane and Ijichi, but it seems that Amane is also an otaku...", + "ja": "\u3042\u307e\u308a\u5927\u304d\u306a\u58f0\u3067\u597d\u304d\u3068\u8a00\u3048\u306a\u3044\u201c\u5973\u5150\u5411\u3051\u30a2\u30cb\u30e1\u201d\u304c\u597d\u304d\u306a\u30aa\u30bf\u30af \u702c\u5c3e\u5353\u4e5f\u3002\u3042\u308b\u65e5\u3001\u30af\u30e9\u30b9\u306e\u30ae\u30e3\u30eb \u5929\u97f3\u3055\u3093\u3068\u4f0a\u5730\u77e5\u3055\u3093\u306b\u7d61\u307e\u308c\u305f\u306e\u3060\u304c\u3001\u4f55\u3084\u3089\u5929\u97f3\u3055\u3093\u3082\u30aa\u30bf\u30af\u306e\u5302\u3044\u304c\u2026\uff1f", + "vi": "M\u1ed9t anh b\u1ea1n trung b\u00ecnh Otaku th\u00edch anime d\u00e0nh cho b\u00e9 g\u00e1i, gi\u00e1p m\u1eb7t hai n\u00e0ng Gyaru xinh \u0111\u1eb9p n\u1ed5i ti\u1ebfng nh\u1ea5t tr\u01b0\u1eddng. Nh\u01b0ng kh\u00f4ng ch\u1ec9 c\u00f3 th\u1ebf, m\u1ed9t trong hai n\u1eef nh\u00e2n xinh \u0111\u1eb9p \u1ea5y c\u00f3 g\u00ec \u0111\u00f3...h\u01a1i otaku th\u00ec ph\u1ea3i...?", + "es-la": "Takuya Seo es un otaku al que le gusta el \u0022anime para chicas\u0022 y no puede decir que le guste en voz alta. Un d\u00eda, se junta con dos chicas de su clase, Amane e Ijichi, pero parece que Amane tambi\u00e9n es una otaku...", + "pt-br": "Takuya Seo \u00e9 um otaku que gosta de \u0022animes para garotinhas\u0022 e n\u00e3o pode dizer isso em voz alta. Um dia, ele conversa com duas gals da sua sala, Amane e Ijichi, mas parece que a Amane tamb\u00e9m \u00e9 uma otaku... Uma com\u00e9dia rom\u00e2ntica escolar onde o otaku conhece as gals que ele gosta!" + }, + "isLocked": false, + "links": { + "al": "138380", + "ap": "otaku-ni-yasashii-gal-wa-inai", + "bw": "series\/339484", + "kt": "69614", + "mu": "188325", + "amz": "https:\/\/www.amazon.co.jp\/dp\/B0BB2R5WVF", + "cdj": "https:\/\/www.cdjapan.co.jp\/searchuni?q=\u30aa\u30bf\u30af\u306b\u512a\u3057\u3044\u30ae\u30e3\u30eb\u306f\u3044\u306a\u3044\uff01\uff1f+(Zenon+Comics)\u0026order=relasc", + "ebj": "https:\/\/ebookjapan.yahoo.co.jp\/books\/690493\/", + "mal": "144152", + "raw": "https:\/\/comic-zenon.com\/episode\/3269754496560134267", + "engtl": "https:\/\/x.com\/yenpress\/status\/1913348424826581290" + }, + "originalLanguage": "ja", + "lastVolume": "", + "lastChapter": "", + "publicationDemographic": "seinen", + "status": "ongoing", + "year": 2021, + "contentRating": "safe", + "tags": [ + { + "id": "423e2eae-a7a2-4a8b-ac03-a8351462d71d", + "type": "tag", + "attributes": { + "name": { "en": "Romance" }, + "description": {}, + "group": "genre", + "version": 1 + }, + "relationships": [] + }, + { + "id": "4d32cc48-9f00-4cca-9b5a-a839f0764984", + "type": "tag", + "attributes": { + "name": { "en": "Comedy" }, + "description": {}, + "group": "genre", + "version": 1 + }, + "relationships": [] + }, + { + "id": "caaa44eb-cd40-4177-b930-79d3ef2afe87", + "type": "tag", + "attributes": { + "name": { "en": "School Life" }, + "description": {}, + "group": "theme", + "version": 1 + }, + "relationships": [] + }, + { + "id": "e5301a23-ebd9-49dd-a0cb-2add944c7fe9", + "type": "tag", + "attributes": { + "name": { "en": "Slice of Life" }, + "description": {}, + "group": "genre", + "version": 1 + }, + "relationships": [] + }, + { + "id": "fad12b5e-68ba-460e-b933-9ae8318f5b65", + "type": "tag", + "attributes": { + "name": { "en": "Gyaru" }, + "description": {}, + "group": "theme", + "version": 1 + }, + "relationships": [] + } + ], + "state": "published", + "chapterNumbersResetOnNewVolume": false, + "createdAt": "2022-03-08T07:41:47+00:00", + "updatedAt": "2025-04-19T23:05:17+00:00", + "version": 44, + "availableTranslatedLanguages": [ "it", "es-la", "en", "id", "fr", "vi", "pt-br" ], + "latestUploadedChapter": "9e8f9776-a8bf-4118-83df-f0d086479d64" + }, + "relationships": [ + { + "id": "518965e7-c26c-4fd9-881c-f7ce0e78323d", + "type": "author", + "attributes": { + "name": "Norishiro-chan", + "imageUrl": null, + "biography": { "en": "Name (in native language)\n\u306e\u308a\u3057\u308d\u3061\u3083\u3093" }, + "twitter": "https:\/\/twitter.com\/norishirochan", + "pixiv": "https:\/\/www.pixiv.net\/en\/users\/821541", + "melonBook": null, + "fanBox": null, + "booth": null, + "namicomi": null, + "nicoVideo": null, + "skeb": null, + "fantia": null, + "tumblr": null, + "youtube": null, + "weibo": null, + "naver": null, + "website": null, + "createdAt": "2021-10-10T03:41:31+00:00", + "updatedAt": "2024-08-13T17:35:37+00:00", + "version": 4 + } + }, + { + "id": "767b8851-6060-478a-a23e-f819fec0fbf2", + "type": "artist", + "attributes": { + "name": "Sakana Uozimi", + "imageUrl": null, + "biography": { "en": "Name (in native language)\n\u9b5a\u4f4f\u3055\u304b\u306a" }, + "twitter": "https:\/\/twitter.com\/namekyunta", + "pixiv": "https:\/\/www.pixiv.net\/en\/users\/468508", + "melonBook": null, + "fanBox": null, + "booth": null, + "namicomi": null, + "nicoVideo": null, + "skeb": null, + "fantia": null, + "tumblr": null, + "youtube": null, + "weibo": null, + "naver": null, + "website": "https:\/\/galleria.emotionflow.com\/11070\/", + "createdAt": "2022-03-08T07:36:27+00:00", + "updatedAt": "2023-11-27T23:10:41+00:00", + "version": 3 + } + }, + { + "id": "a06943fd-6309-49a8-a66a-8df0f6dc41eb", + "type": "cover_art", + "attributes": { + "description": "Volume 9 Cover from BookLive", + "volume": "9", + "fileName": "6b3073de-bb65-4723-8113-6068bf8c6eb4.jpg", + "locale": "ja", + "createdAt": "2025-02-20T11:59:45+00:00", + "updatedAt": "2025-02-20T11:59:45+00:00", + "version": 1 + } + }, + { + "id": "6c0a87bf-3adb-4854-a4a6-d5c358b73d21", + "type": "creator" + } + ] + } +} \ No newline at end of file diff --git a/MangaReader.Tests/WebCrawlers/NatoManga/NatoMangaWebCrawlerTests.cs b/MangaReader.Tests/WebCrawlers/NatoManga/NatoMangaWebCrawlerTests.cs index 8ab85cc..c4a258f 100644 --- a/MangaReader.Tests/WebCrawlers/NatoManga/NatoMangaWebCrawlerTests.cs +++ b/MangaReader.Tests/WebCrawlers/NatoManga/NatoMangaWebCrawlerTests.cs @@ -1,4 +1,4 @@ -using MangaReader.Core.WebCrawlers.NatoManga; +using MangaReader.Core.Sources.NatoManga.Metadata; using Shouldly; namespace MangaReader.Tests.WebCrawlers.NatoManga; diff --git a/MangaReader.Tests/WebCrawlers/UnitTest1.cs b/MangaReader.Tests/WebCrawlers/UnitTest1.cs index 1512e39..36ed513 100644 --- a/MangaReader.Tests/WebCrawlers/UnitTest1.cs +++ b/MangaReader.Tests/WebCrawlers/UnitTest1.cs @@ -1,5 +1,5 @@ -using MangaReader.Core.WebCrawlers; -using MangaReader.Core.WebCrawlers.MangaNato; +using MangaReader.Core.Metadata; +using MangaReader.Core.Sources.MangaNato.Metadata; using Shouldly; namespace MangaReader.Tests.WebCrawlers;