using MangaReader.Core.Common; using MangaReader.Core.Metadata; using MangaReader.Core.Sources.MangaDex.Api; namespace MangaReader.Core.Sources.MangaDex.Metadata; public class MangaDexMetadataProvider(IMangaDexClient mangaDexClient) : IMangaMetadataProvider { public string SourceId => "MangaDex"; public async Task GetMangaAsync(string url, CancellationToken cancellationToken) { Guid mangaGuid = GetSourceMangaGuid(url); MangaDexResponse? mangaDexResponse = await mangaDexClient.GetMangaAsync(mangaGuid, cancellationToken); if (mangaDexResponse == null || mangaDexResponse is not MangaDexEntityResponse mangaDexEntityResponse) return null; if (mangaDexEntityResponse.Data == null || mangaDexEntityResponse.Data is not MangaEntity mangaEntity) return null; if (mangaEntity.Attributes == null) return null; MangaAttributes mangaAttributes = mangaEntity.Attributes; List mangaRelationships = mangaEntity.Relationships; MangaDexResponse? mangaDexFeedResponse = await mangaDexClient.GetFeedAsync(mangaGuid, cancellationToken); MangaDexResponse? coverArtResponse = await mangaDexClient.GetCoverArtAsync(mangaGuid, cancellationToken); return new SourceManga() { Title = GetTitle(mangaAttributes), AlternateTitles = GetAlternateTitles(mangaAttributes), Genres = GetGenres(mangaAttributes), Contributors = GetContributors(mangaRelationships), Chapters = GetChapters(mangaDexFeedResponse), CoverArt = GetCoverArt(mangaGuid, coverArtResponse) }; } 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 static SourceMangaTitle GetTitle(MangaAttributes attributes) { return new() { Name = GetTileName(attributes), Language = Language.English }; } private static string GetTileName(MangaAttributes attributes) { if (attributes.Title.TryGetValue("en", out string? title)) return title; return string.Empty; } private static List GetAlternateTitles(MangaAttributes attributes) { if (attributes.AltTitles == null || attributes.AltTitles.Count == 0) return []; Dictionary languageIdMap = new() { { "en", Language.English }, { "ja", Language.Japanese }, { "ja-ro", Language.Romaji }, }; List sourceMangaTitles = []; foreach (Dictionary alternateTitle in attributes.AltTitles) { foreach (string alternateTitleKey in alternateTitle.Keys) { if (languageIdMap.TryGetValue(alternateTitleKey, out Language language) == false) continue; SourceMangaTitle sourceMangaTitle = new() { Name = alternateTitle[alternateTitleKey], Language = language }; sourceMangaTitles.Add(sourceMangaTitle); } } return sourceMangaTitles; } private static List GetGenres(MangaAttributes attributes) { if (attributes.Tags == null || attributes.Tags.Count == 0) return []; List tags = []; foreach (TagEntity tagEntity in attributes.Tags) { if (tagEntity.Attributes == null) continue; if (tagEntity.Attributes.Name == null || tagEntity.Attributes.Name.Count == 0) continue; tags.Add(tagEntity.Attributes.Name.FirstOrDefault().Value); } return tags; } private static SourceMangaContributor[] GetContributors(List relationships) { List contributors = []; AuthorEntity[] authorEntities = [.. relationships.Where(entity => entity is AuthorEntity).Cast()]; ArtistEntity[] artistEntities = [.. relationships.Where(entity => entity is ArtistEntity).Cast()]; foreach (AuthorEntity authorEntity in authorEntities) { if (authorEntity.Attributes == null) continue; SourceMangaContributor contributor = new() { Name = authorEntity.Attributes.Name, Role = SourceMangaContributorRole.Author }; contributors.Add(contributor); } foreach (ArtistEntity artistEntity in artistEntities) { if (artistEntity.Attributes == null) continue; SourceMangaContributor contributor = new() { Name = artistEntity.Attributes.Name, Role = SourceMangaContributorRole.Artist }; contributors.Add(contributor); } return [.. contributors]; } private static List GetChapters(MangaDexResponse? mangaDexFeedResponse) { if (mangaDexFeedResponse == null || mangaDexFeedResponse is not MangaDexCollectionResponse collectionResponse) return []; //https://mangadex.org/chapter/46084762-855c-46dd-a7b6-66e5cd15604d List chapters = []; ChapterEntity[] chapterEntities = [.. collectionResponse.Data.Where(entity => entity is ChapterEntity).Cast()]; foreach (ChapterEntity chapterEntity in chapterEntities) { if (chapterEntity.Attributes == null || chapterEntity.Attributes.TranslatedLanguage != "en") continue; int? volume = int.TryParse(chapterEntity.Attributes.Volume, out var temp) ? temp : null; if (float.TryParse(chapterEntity.Attributes.Chapter, out float chapterNumber) == false) continue; SourceMangaChapter chapter = new() { Volume = volume, Number = chapterNumber, Title = chapterEntity.Attributes.Title, Url = $"https://mangadex.org/chapter/{chapterEntity.Id}" }; chapters.Add(chapter); } return chapters; } private static string[] GetCoverArt(Guid mangaGuid, MangaDexResponse? coverArtResponse) { if (coverArtResponse == null || coverArtResponse is not MangaDexCollectionResponse collectionResponse) return []; List coverArtUrls = []; CoverArtEntity[] coverArtEntities = [.. collectionResponse.Data.Where(entity => entity is CoverArtEntity).Cast()]; 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]; } }