Added MangaDex cover art logic. Added "Search Manga" test for MangaDex client.
This commit is contained in:
@@ -13,4 +13,5 @@ public class SourceManga
|
||||
public float? RatingPercent { get; set; }
|
||||
public int? Votes { get; set; }
|
||||
public List<SourceMangaChapter> Chapters { get; set; } = [];
|
||||
public string[] CoverArt { get; set; } = [];
|
||||
}
|
||||
@@ -8,4 +8,5 @@ public interface IMangaDexClient
|
||||
Task<MangaDexResponse?> GetMangaAsync(Guid mangaGuid, CancellationToken cancellationToken);
|
||||
Task<MangaDexResponse?> GetFeedAsync(Guid mangaGuid, CancellationToken cancellationToken);
|
||||
Task<MangaDexChapterResponse?> GetChapterAsync(Guid chapterGuid, CancellationToken cancellationToken);
|
||||
Task<MangaDexResponse?> GetCoverArtAsync(Guid mangaGuid, CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -69,5 +69,10 @@ namespace MangaReader.Core.Sources.MangaDex.Api
|
||||
|
||||
return JsonSerializer.Deserialize<MangaDexChapterResponse>(response, _jsonSerializerOptions);
|
||||
}
|
||||
|
||||
public async Task<MangaDexResponse?> GetCoverArtAsync(Guid mangaGuid, CancellationToken cancellationToken)
|
||||
{
|
||||
return await GetAsync($"https://api.mangadex.org/cover?order[volume]=asc&manga[]={mangaGuid}&limit=100&offset=0", cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe
|
||||
MangaAttributes mangaAttributes = mangaEntity.Attributes;
|
||||
List<MangaDexEntity> mangaRelationships = mangaEntity.Relationships;
|
||||
MangaDexResponse? mangaDexFeedResponse = await mangaDexClient.GetFeedAsync(mangaGuid, cancellationToken);
|
||||
MangaDexResponse? coverArtResponse = await mangaDexClient.GetCoverArtAsync(mangaGuid, cancellationToken);
|
||||
|
||||
return new SourceManga()
|
||||
{
|
||||
@@ -31,7 +32,8 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe
|
||||
AlternateTitles = GetAlternateTitles(mangaAttributes),
|
||||
Genres = GetGenres(mangaAttributes),
|
||||
Contributors = GetContributors(mangaRelationships),
|
||||
Chapters = GetChapters(mangaDexFeedResponse)
|
||||
Chapters = GetChapters(mangaDexFeedResponse),
|
||||
CoverArt = GetCoverArt(mangaGuid, coverArtResponse)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -181,4 +183,25 @@ public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMe
|
||||
|
||||
return chapters;
|
||||
}
|
||||
|
||||
private static string[] GetCoverArt(Guid mangaGuid, MangaDexResponse? coverArtResponse)
|
||||
{
|
||||
if (coverArtResponse == null || coverArtResponse is not MangaDexCollectionResponse collectionResponse)
|
||||
return [];
|
||||
|
||||
List<string> coverArtUrls = [];
|
||||
CoverArtEntity[] coverArtEntities = [.. collectionResponse.Data.Where(entity => entity is CoverArtEntity).Cast<CoverArtEntity>()];
|
||||
|
||||
foreach (CoverArtEntity coverArtEntity in coverArtEntities)
|
||||
{
|
||||
if (coverArtEntity.Attributes == null || string.IsNullOrWhiteSpace(coverArtEntity.Attributes.FileName))
|
||||
continue;
|
||||
|
||||
string url = $"https://mangadex.org/covers/{mangaGuid}/{coverArtEntity.Attributes.FileName}";
|
||||
|
||||
coverArtUrls.Add(url);
|
||||
}
|
||||
|
||||
return [.. coverArtUrls];
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Sources\MangaDex\Api\Manga-Chapter-Response.json" />
|
||||
<None Remove="Sources\MangaDex\Api\Manga-Cover-Art-Response.json" />
|
||||
<None Remove="WebCrawlers\Samples\MangaNato - Please Go Home, Akutsu-San!.htm" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -25,6 +26,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Chapter-Response.json" />
|
||||
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Cover-Art-Response.json" />
|
||||
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Search-Response.json" />
|
||||
<EmbeddedResource Include="Sources\NatoManga\Api\Manga-Search-Response.json" />
|
||||
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Feed-Response.json" />
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
{
|
||||
"result": "ok",
|
||||
"response": "collection",
|
||||
"data": [
|
||||
{
|
||||
"id": "0045f243-5625-4f0f-9066-a6c3a95d84d3",
|
||||
"type": "cover_art",
|
||||
"attributes": {
|
||||
"description": "",
|
||||
"volume": "1",
|
||||
"fileName": "2569ffd8-4ba1-4030-8d08-b7a21333a7a6.jpg",
|
||||
"locale": "ja",
|
||||
"createdAt": "2024-06-18T14:42:11+00:00",
|
||||
"updatedAt": "2024-06-18T14:42:11+00:00",
|
||||
"version": 1
|
||||
},
|
||||
"relationships": [
|
||||
{
|
||||
"id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec",
|
||||
"type": "manga"
|
||||
},
|
||||
{
|
||||
"id": "f73d34f6-f678-4f8b-9dff-5c759d5fc767",
|
||||
"type": "user"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a81ad8d3-ba2c-4003-9126-fbd9d28e3732",
|
||||
"type": "cover_art",
|
||||
"attributes": {
|
||||
"description": "",
|
||||
"volume": "2",
|
||||
"fileName": "d2314e9b-4287-4e65-8045-b713d97c0b28.jpg",
|
||||
"locale": "ja",
|
||||
"createdAt": "2024-06-18T14:42:15+00:00",
|
||||
"updatedAt": "2024-06-18T14:42:15+00:00",
|
||||
"version": 1
|
||||
},
|
||||
"relationships": [
|
||||
{
|
||||
"id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec",
|
||||
"type": "manga"
|
||||
},
|
||||
{
|
||||
"id": "f73d34f6-f678-4f8b-9dff-5c759d5fc767",
|
||||
"type": "user"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "0ca43181-8ae1-4e5f-934f-4ea407e05913",
|
||||
"type": "cover_art",
|
||||
"attributes": {
|
||||
"description": "",
|
||||
"volume": "3",
|
||||
"fileName": "ed4715de-fc1b-4f50-9e12-9d2ba99e044f.jpg",
|
||||
"locale": "ja",
|
||||
"createdAt": "2024-06-18T14:42:15+00:00",
|
||||
"updatedAt": "2024-06-18T14:42:15+00:00",
|
||||
"version": 1
|
||||
},
|
||||
"relationships": [
|
||||
{
|
||||
"id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec",
|
||||
"type": "manga"
|
||||
},
|
||||
{
|
||||
"id": "f73d34f6-f678-4f8b-9dff-5c759d5fc767",
|
||||
"type": "user"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "1dce1a43-86fb-4db6-86ca-fbc4b6c5cfab",
|
||||
"type": "cover_art",
|
||||
"attributes": {
|
||||
"description": "",
|
||||
"volume": "4",
|
||||
"fileName": "b4c335cc-0e4d-4407-86ff-61e41f817e83.jpg",
|
||||
"locale": "ja",
|
||||
"createdAt": "2024-06-18T14:42:14+00:00",
|
||||
"updatedAt": "2024-06-18T14:42:14+00:00",
|
||||
"version": 1
|
||||
},
|
||||
"relationships": [
|
||||
{
|
||||
"id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec",
|
||||
"type": "manga"
|
||||
},
|
||||
{
|
||||
"id": "f73d34f6-f678-4f8b-9dff-5c759d5fc767",
|
||||
"type": "user"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "c8151575-1aac-4464-a99f-6cafa1f962c5",
|
||||
"type": "cover_art",
|
||||
"attributes": {
|
||||
"description": "",
|
||||
"volume": "5",
|
||||
"fileName": "66323609-ba33-4ade-8c64-3b08c346e6da.jpg",
|
||||
"locale": "ja",
|
||||
"createdAt": "2024-06-18T14:42:11+00:00",
|
||||
"updatedAt": "2024-06-18T14:42:11+00:00",
|
||||
"version": 1
|
||||
},
|
||||
"relationships": [
|
||||
{
|
||||
"id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec",
|
||||
"type": "manga"
|
||||
},
|
||||
{
|
||||
"id": "f73d34f6-f678-4f8b-9dff-5c759d5fc767",
|
||||
"type": "user"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dbac6a58-4d97-4ec9-85e3-fb4a4d904590",
|
||||
"type": "cover_art",
|
||||
"attributes": {
|
||||
"description": "",
|
||||
"volume": "6",
|
||||
"fileName": "fc9d5eb5-3179-4543-ae6b-88d3231fca5b.jpg",
|
||||
"locale": "ja",
|
||||
"createdAt": "2024-06-18T14:42:15+00:00",
|
||||
"updatedAt": "2024-06-18T14:42:15+00:00",
|
||||
"version": 1
|
||||
},
|
||||
"relationships": [
|
||||
{
|
||||
"id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec",
|
||||
"type": "manga"
|
||||
},
|
||||
{
|
||||
"id": "f73d34f6-f678-4f8b-9dff-5c759d5fc767",
|
||||
"type": "user"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "d7158641-029d-4763-b621-fbdaa83ed3c4",
|
||||
"type": "cover_art",
|
||||
"attributes": {
|
||||
"description": "",
|
||||
"volume": "7",
|
||||
"fileName": "61f990f0-103a-4967-ac64-01dc9938cb5c.jpg",
|
||||
"locale": "ja",
|
||||
"createdAt": "2024-06-18T14:42:12+00:00",
|
||||
"updatedAt": "2024-06-18T14:42:12+00:00",
|
||||
"version": 1
|
||||
},
|
||||
"relationships": [
|
||||
{
|
||||
"id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec",
|
||||
"type": "manga"
|
||||
},
|
||||
{
|
||||
"id": "f73d34f6-f678-4f8b-9dff-5c759d5fc767",
|
||||
"type": "user"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "9cbbbcfc-e82d-4c83-9d82-de692f52faf1",
|
||||
"type": "cover_art",
|
||||
"attributes": {
|
||||
"description": "",
|
||||
"volume": "8",
|
||||
"fileName": "4b87e456-0243-4dfe-abda-d0f41c91141a.jpg",
|
||||
"locale": "ja",
|
||||
"createdAt": "2024-10-16T10:40:16+00:00",
|
||||
"updatedAt": "2024-10-16T10:40:16+00:00",
|
||||
"version": 1
|
||||
},
|
||||
"relationships": [
|
||||
{
|
||||
"id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec",
|
||||
"type": "manga"
|
||||
},
|
||||
{
|
||||
"id": "f73d34f6-f678-4f8b-9dff-5c759d5fc767",
|
||||
"type": "user"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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
|
||||
},
|
||||
"relationships": [
|
||||
{
|
||||
"id": "ee96e2b7-9af2-4864-9656-649f4d3b6fec",
|
||||
"type": "manga"
|
||||
},
|
||||
{
|
||||
"id": "27bde0e8-71b0-4bf2-8e25-2902a7b2dd4b",
|
||||
"type": "user"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"limit": 100,
|
||||
"offset": 0,
|
||||
"total": 9
|
||||
}
|
||||
@@ -22,6 +22,52 @@ public class MangaDexClientTests
|
||||
MangaDexResponse? mangaDexResponse = await mangaDexClient.SearchMangaByTitleAsync("Some random text", CancellationToken.None);
|
||||
|
||||
// Testing here
|
||||
mangaDexResponse.ShouldNotBeNull();
|
||||
mangaDexResponse.Response.ShouldBe("collection");
|
||||
mangaDexResponse.ShouldBeOfType<MangaDexCollectionResponse>();
|
||||
|
||||
MangaDexCollectionResponse mangaDexCollectionResponse = (mangaDexResponse as MangaDexCollectionResponse)!;
|
||||
mangaDexCollectionResponse.Data.Count.ShouldBe(3);
|
||||
|
||||
mangaDexCollectionResponse.Data[0].ShouldBeOfType<MangaEntity>();
|
||||
MangaEntity mangaEntity = (mangaDexCollectionResponse.Data[0] as MangaEntity)!;
|
||||
|
||||
mangaEntity.Attributes.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.ShouldNotBeNull();
|
||||
mangaEntity.Attributes.Tags[0].Attributes!.Name.ShouldContainKey("en");
|
||||
mangaEntity.Attributes.Tags[0].Attributes!.Name["en"].ShouldBe("Romance");
|
||||
|
||||
mangaEntity.Attributes.Tags[1].Attributes.ShouldNotBeNull();
|
||||
mangaEntity.Attributes.Tags[1].Attributes!.Name.ShouldContainKey("en");
|
||||
mangaEntity.Attributes.Tags[1].Attributes!.Name["en"].ShouldBe("Comedy");
|
||||
|
||||
mangaEntity.Attributes.Tags[2].Attributes.ShouldNotBeNull();
|
||||
mangaEntity.Attributes.Tags[2].Attributes!.Name.ShouldContainKey("en");
|
||||
mangaEntity.Attributes.Tags[2].Attributes!.Name["en"].ShouldBe("School Life");
|
||||
|
||||
mangaEntity.Attributes.Tags[3].Attributes.ShouldNotBeNull();
|
||||
mangaEntity.Attributes.Tags[3].Attributes!.Name.ShouldContainKey("en");
|
||||
mangaEntity.Attributes.Tags[3].Attributes!.Name["en"].ShouldBe("Slice of Life");
|
||||
|
||||
mangaEntity.Attributes.Tags[4].Attributes.ShouldNotBeNull();
|
||||
mangaEntity.Attributes.Tags[4].Attributes!.Name.ShouldContainKey("en");
|
||||
mangaEntity.Attributes.Tags[4].Attributes!.Name["en"].ShouldBe("Gyaru");
|
||||
|
||||
mangaEntity.Relationships.Count.ShouldBe(4);
|
||||
mangaEntity.Relationships[2].ShouldBeOfType<CoverArtEntity>();
|
||||
|
||||
CoverArtEntity coverArtEntity = (mangaEntity.Relationships[2] as CoverArtEntity)!;
|
||||
coverArtEntity.Attributes.ShouldNotBeNull();
|
||||
coverArtEntity.Attributes.FileName.ShouldBe("6b3073de-bb65-4723-8113-6068bf8c6eb4.jpg");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -154,6 +200,35 @@ public class MangaDexClientTests
|
||||
mangaDexChapterResponse.Chapter.DataSaver[12].ShouldBe("13-b886b4ed986a473478e3db7bb18fe2faea567a1ad5e520408967410dcf8838d1.jpg");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_Cover_Art()
|
||||
{
|
||||
string searchResultJson = await ReadJsonResourceAsync("Manga-Cover-Art-Response.json");
|
||||
|
||||
IHttpService httpService = Substitute.For<IHttpService>();
|
||||
|
||||
httpService.GetStringAsync(Arg.Any<string>(), CancellationToken.None)
|
||||
.Returns(Task.FromResult(searchResultJson));
|
||||
|
||||
MangaDexClient mangaDexClient = new(httpService);
|
||||
MangaDexResponse? mangaDexResponse = await mangaDexClient.GetCoverArtAsync(Guid.NewGuid(), CancellationToken.None);
|
||||
|
||||
mangaDexResponse.ShouldNotBeNull();
|
||||
mangaDexResponse.Response.ShouldBe("collection");
|
||||
mangaDexResponse.ShouldBeOfType<MangaDexCollectionResponse>();
|
||||
|
||||
MangaDexCollectionResponse mangaDexEntityResponse = (mangaDexResponse as MangaDexCollectionResponse)!;
|
||||
|
||||
List<CoverArtEntity> coverArtEntities = [.. mangaDexEntityResponse.Data.Where(entity => entity is CoverArtEntity).Cast<CoverArtEntity>()];
|
||||
coverArtEntities.Count.ShouldBe(9);
|
||||
|
||||
coverArtEntities[0].Attributes.ShouldNotBeNull();
|
||||
coverArtEntities[0].Attributes!.FileName.ShouldBe("2569ffd8-4ba1-4030-8d08-b7a21333a7a6.jpg");
|
||||
|
||||
coverArtEntities[8].Attributes.ShouldNotBeNull();
|
||||
coverArtEntities[8].Attributes!.FileName.ShouldBe("6b3073de-bb65-4723-8113-6068bf8c6eb4.jpg");
|
||||
}
|
||||
|
||||
private static async Task<string> ReadJsonResourceAsync(string resourceName)
|
||||
{
|
||||
return await ResourceHelper.ReadJsonResourceAsync($"MangaReader.Tests.Sources.MangaDex.Api.{resourceName}");
|
||||
|
||||
@@ -11,7 +11,7 @@ public class MangaDexMetadataTests
|
||||
[Fact]
|
||||
public async Task Get_Manga()
|
||||
{
|
||||
MangaDexEntityResponse entityResponse = new()
|
||||
MangaDexEntityResponse mangaEntityResponse = new()
|
||||
{
|
||||
Result = "ok",
|
||||
Response = "entity",
|
||||
@@ -141,7 +141,7 @@ public class MangaDexMetadataTests
|
||||
}
|
||||
};
|
||||
|
||||
MangaDexCollectionResponse collectionResponse = new()
|
||||
MangaDexCollectionResponse feedCollectionResponse = new()
|
||||
{
|
||||
Result = "ok",
|
||||
Response = "collection",
|
||||
@@ -186,13 +186,43 @@ public class MangaDexMetadataTests
|
||||
]
|
||||
};
|
||||
|
||||
MangaDexCollectionResponse coverArtCollectionResponse = new()
|
||||
{
|
||||
Result = "ok",
|
||||
Response = "collection",
|
||||
Data =
|
||||
[
|
||||
new CoverArtEntity()
|
||||
{
|
||||
Id = new Guid("0045f243-5625-4f0f-9066-a6c3a95d84d3"),
|
||||
Type = "cover_art",
|
||||
Attributes = new()
|
||||
{
|
||||
FileName = "2569ffd8-4ba1-4030-8d08-b7a21333a7a6.jpg"
|
||||
}
|
||||
},
|
||||
new CoverArtEntity()
|
||||
{
|
||||
Id = new Guid("a81ad8d3-ba2c-4003-9126-fbd9d28e3732"),
|
||||
Type = "cover_art",
|
||||
Attributes = new()
|
||||
{
|
||||
FileName = "d2314e9b-4287-4e65-8045-b713d97c0b28.jpg"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
IMangaDexClient mangaDexClient = Substitute.For<IMangaDexClient>();
|
||||
|
||||
mangaDexClient.GetMangaAsync(Arg.Any<Guid>(), CancellationToken.None)
|
||||
.Returns(entityResponse);
|
||||
.Returns(mangaEntityResponse);
|
||||
|
||||
mangaDexClient.GetFeedAsync(Arg.Any<Guid>(), CancellationToken.None)
|
||||
.Returns(collectionResponse);
|
||||
.Returns(feedCollectionResponse);
|
||||
|
||||
mangaDexClient.GetCoverArtAsync(Arg.Any<Guid>(), CancellationToken.None)
|
||||
.Returns(coverArtCollectionResponse);
|
||||
|
||||
MangaDexMetadataProvider metadataProvider = new(mangaDexClient);
|
||||
SourceManga? sourceManga = await metadataProvider.GetMangaAsync("https://mangadex.org/title/ee96e2b7-9af2-4864-9656-649f4d3b6fec/gals-can-t-be-kind-to-otaku", CancellationToken.None);
|
||||
@@ -248,5 +278,9 @@ public class MangaDexMetadataTests
|
||||
sourceManga.Chapters[2].Number.ShouldBe(8.1f);
|
||||
sourceManga.Chapters[2].Title.ShouldBe("Otaku & Gyaru & Protegee");
|
||||
sourceManga.Chapters[2].Url.ShouldBe("https://mangadex.org/chapter/b5206e9b-6e3e-4ef0-aa62-381fd0ff75a5");
|
||||
|
||||
sourceManga.CoverArt.Length.ShouldBe(2);
|
||||
sourceManga.CoverArt[0].ShouldBe("https://mangadex.org/covers/ee96e2b7-9af2-4864-9656-649f4d3b6fec/2569ffd8-4ba1-4030-8d08-b7a21333a7a6.jpg");
|
||||
sourceManga.CoverArt[1].ShouldBe("https://mangadex.org/covers/ee96e2b7-9af2-4864-9656-649f4d3b6fec/d2314e9b-4287-4e65-8045-b713d97c0b28.jpg");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user