213 lines
7.4 KiB
C#
213 lines
7.4 KiB
C#
using MangaReader.Core.Search;
|
|
using MangaReader.Core.Sources.MangaDex.Api;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace MangaReader.Core.Sources.MangaDex.Search;
|
|
|
|
public partial class MangaDexSearchProvider(IMangaDexClient mangaDexClient) : IMangaSearchProvider
|
|
{
|
|
[GeneratedRegex(@"[^a-z0-9\s-]")]
|
|
private static partial Regex InvalidSlugCharactersRegex();
|
|
|
|
[GeneratedRegex(@"\s+")]
|
|
private static partial Regex WhitespaceRegex();
|
|
|
|
public string SourceId => "MangaDex";
|
|
|
|
public async Task<MangaSearchResult[]> SearchAsync(string keyword, CancellationToken cancellationToken)
|
|
{
|
|
MangaDexResponse? response = await mangaDexClient.SearchMangaByTitleAsync(keyword, cancellationToken);
|
|
|
|
if (response == null || (response is not MangaDexCollectionResponse collectionResponse))
|
|
return [];
|
|
|
|
MangaEntity[] mangaEntities = [.. collectionResponse.Data.Where(entity => entity is MangaEntity).Cast<MangaEntity>()];
|
|
|
|
if (mangaEntities.Length == 0)
|
|
return [];
|
|
|
|
Dictionary<Guid, List<CoverArtEntity>> mangaCoverArtMap = await GetCoverArtFileNamesAsync(mangaEntities, cancellationToken);
|
|
|
|
List<MangaSearchResult> mangaSearchResults = [];
|
|
Dictionary<Guid, MangaSearchResult> thing = [];
|
|
|
|
foreach (MangaEntity mangaEntity in mangaEntities)
|
|
{
|
|
CoverArtEntity[] coverArtEntites = [.. mangaCoverArtMap[mangaEntity.Id]];
|
|
|
|
MangaSearchResult? mangaSearchResult = GetMangaSearchResult(mangaEntity, coverArtEntites);
|
|
|
|
if (mangaSearchResult == null)
|
|
continue;
|
|
|
|
mangaSearchResults.Add(mangaSearchResult);
|
|
}
|
|
|
|
if (thing.Count > 0)
|
|
{
|
|
Guid[] mangaGuids = [.. thing.Select(x => x.Key)];
|
|
var reults = await GetCoverArtFileNamesAsync(mangaGuids, cancellationToken);
|
|
//var reults = await mangaDexClient.GetCoverArtAsync(mangaGuids, cancellationToken);
|
|
}
|
|
|
|
return [.. mangaSearchResults];
|
|
}
|
|
|
|
private MangaSearchResult? GetMangaSearchResult(MangaEntity mangaEntity, CoverArtEntity[] coverArtEntites)
|
|
{
|
|
MangaAttributes? mangaAttributes = mangaEntity.Attributes;
|
|
|
|
if (mangaAttributes == null)
|
|
return null;
|
|
|
|
string title = GetTitle(mangaAttributes);
|
|
string slug = GenerateSlug(title);
|
|
|
|
MangaSearchResult mangaSearchResult = new()
|
|
{
|
|
Source = SourceId,
|
|
Title = title,
|
|
Description = GetDescription(mangaAttributes),
|
|
Genres = GetGenres(mangaAttributes),
|
|
Url = $"https://mangadex.org/title/{mangaEntity.Id}/{slug}",
|
|
Thumbnail = GetThumbnail(mangaEntity, coverArtEntites)
|
|
};
|
|
|
|
return mangaSearchResult;
|
|
}
|
|
|
|
private static string GetTitle(MangaAttributes attributes)
|
|
{
|
|
var alternateTitle = attributes.AltTitles.Where(x => x.ContainsKey("en")).FirstOrDefault();
|
|
|
|
if (alternateTitle?.Count > 0)
|
|
return alternateTitle["en"];
|
|
|
|
if (attributes.Title.TryGetValue("en", out string? title))
|
|
return title;
|
|
|
|
if (attributes.Title.Count > 0)
|
|
return attributes.Title.ElementAt(0).Value;
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
private static string GetDescription(MangaAttributes attributes)
|
|
{
|
|
if (attributes.Description.TryGetValue("en", out string? description))
|
|
return description;
|
|
|
|
if (attributes.Description.Count > 0)
|
|
return attributes.Description.ElementAt(0).Value;
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
private static string[] GetGenres(MangaAttributes attributes)
|
|
{
|
|
if (attributes.Tags == null || attributes.Tags.Count == 0)
|
|
return [];
|
|
|
|
List<string> 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];
|
|
}
|
|
|
|
public static string GenerateSlug(string title)
|
|
{
|
|
// title.ToLowerInvariant().Normalize(NormalizationForm.FormD);
|
|
|
|
title = title.ToLowerInvariant();
|
|
//title = InvalidSlugCharactersRegex().Replace(title, ""); // remove invalid chars
|
|
title = InvalidSlugCharactersRegex().Replace(title, "-"); // replace invalid chars with dash
|
|
title = WhitespaceRegex().Replace(title, "-"); // replace spaces with dash
|
|
|
|
return title.Trim('-');
|
|
}
|
|
|
|
private static string? GetThumbnail(MangaDexEntity mangaDexEntity, CoverArtEntity[] coverArtEntites)
|
|
{
|
|
string? fileName = GetCoverArtFileNameFromMangaEntity(mangaDexEntity)
|
|
?? GetCoverArtFileNameFromCoverArtEntities(coverArtEntites);
|
|
|
|
if (string.IsNullOrWhiteSpace(fileName))
|
|
return null;
|
|
|
|
return $"https://mangadex.org/covers/{mangaDexEntity.Id}/{fileName}";
|
|
}
|
|
|
|
private static string? GetCoverArtFileNameFromMangaEntity(MangaDexEntity mangaDexEntity)
|
|
{
|
|
CoverArtEntity? coverArtEntity = (CoverArtEntity?)mangaDexEntity.Relationships.FirstOrDefault(entity =>
|
|
entity is CoverArtEntity);
|
|
|
|
if (coverArtEntity == null || string.IsNullOrWhiteSpace(coverArtEntity.Attributes?.FileName))
|
|
return null;
|
|
|
|
return coverArtEntity.Attributes?.FileName;
|
|
}
|
|
|
|
private static string? GetCoverArtFileNameFromCoverArtEntities(CoverArtEntity[] coverArtEntites)
|
|
{
|
|
return coverArtEntites.Where(coverArtEntity =>
|
|
string.IsNullOrWhiteSpace(coverArtEntity.Attributes?.FileName) == false).FirstOrDefault()?.Attributes!.FileName;
|
|
}
|
|
|
|
private async Task<Dictionary<Guid, List<CoverArtEntity>>> GetCoverArtFileNamesAsync(MangaEntity[] mangaEntities, CancellationToken cancellationToken)
|
|
{
|
|
Guid[] mangaGuids = [.. mangaEntities.Select(entity => entity.Id)];
|
|
|
|
return await GetCoverArtFileNamesAsync(mangaGuids, cancellationToken);
|
|
}
|
|
|
|
private async Task<Dictionary<Guid, List<CoverArtEntity>>> GetCoverArtFileNamesAsync(Guid[] mangaGuids, CancellationToken cancellationToken)
|
|
{
|
|
Dictionary<Guid, List<CoverArtEntity>> result = [];
|
|
|
|
foreach (Guid mangaGuid in mangaGuids)
|
|
{
|
|
result.Add(mangaGuid, []);
|
|
}
|
|
|
|
MangaDexResponse? response = await mangaDexClient.GetCoverArtAsync(mangaGuids, cancellationToken);
|
|
|
|
if (response == null || (response is not MangaDexCollectionResponse collectionResponse))
|
|
return result;
|
|
|
|
CoverArtEntity[] coverArtEntities = [.. collectionResponse.Data.Where(entity => entity is CoverArtEntity).Cast<CoverArtEntity>()];
|
|
|
|
if (coverArtEntities.Length == 0)
|
|
return result;
|
|
|
|
CoverArtEntity[] orderedCoverArtEntities = [.. coverArtEntities.OrderBy(x => x.Attributes?.Volume)];
|
|
|
|
foreach (var coverArtEntity in orderedCoverArtEntities)
|
|
{
|
|
if (coverArtEntity.Attributes == null)
|
|
continue;
|
|
|
|
MangaEntity? mangaEntity = (MangaEntity?)coverArtEntity.Relationships.FirstOrDefault(relationship => relationship is MangaEntity);
|
|
|
|
if (mangaEntity == null)
|
|
continue;
|
|
|
|
if (result.ContainsKey(mangaEntity.Id) == false)
|
|
continue;
|
|
|
|
result[mangaEntity.Id].Add(coverArtEntity);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
} |