MetadataFetching:

- Jikan (MAL) linking, fetching/updating
This commit is contained in:
2025-06-28 22:59:35 +02:00
parent 8170e1d762
commit 2eb0d941b2
12 changed files with 1190 additions and 0 deletions

View File

@ -1,5 +1,6 @@
using API.Schema.Jobs;
using API.Schema.MangaConnectors;
using API.Schema.MetadataFetchers;
using log4net;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
@ -15,6 +16,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
public DbSet<Chapter> Chapters { get; set; }
public DbSet<Author> Authors { get; set; }
public DbSet<MangaTag> Tags { get; set; }
public DbSet<MetadataEntry> MetadataEntries { get; set; }
private ILog Log => LogManager.GetLogger(GetType());
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
@ -191,5 +193,18 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
modelBuilder.Entity<Manga>()
.Navigation(m => m.Library)
.AutoInclude();
modelBuilder.Entity<MetadataFetcher>()
.HasDiscriminator<string>(nameof(MetadataEntry))
.HasValue<MyAnimeList>(nameof(MyAnimeList));
//MetadataEntry
modelBuilder.Entity<MetadataEntry>()
.HasOne<Manga>(entry => entry.Manga)
.WithMany()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MetadataEntry>()
.HasOne<MetadataFetcher>(entry => entry.MetadataFetcher)
.WithMany()
.OnDelete(DeleteBehavior.Cascade);
}
}

View File

@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
namespace API.Schema.MetadataFetchers;
[PrimaryKey("MangaId", "MetadataFetcherName")]
public class MetadataEntry
{
[JsonIgnore]
public Manga Manga { get; init; } = null!;
public string MangaId { get; init; }
[JsonIgnore]
public MetadataFetcher MetadataFetcher { get; init; } = null!;
public string MetadataFetcherName { get; init; }
public string Identifier { get; init; }
public MetadataEntry(MetadataFetcher fetcher, Manga manga, string identifier)
{
this.Manga = manga;
this.MangaId = manga.MangaId;
this.MetadataFetcher = fetcher;
this.MetadataFetcherName = fetcher.MetadataFetcherName;
this.Identifier = identifier;
}
/// <summary>
/// EFCORE only!!!!
/// </summary>
internal MetadataEntry(string mangaId, string identifier, string metadataFetcherName)
{
this.MangaId = mangaId;
this.Identifier = identifier;
this.MetadataFetcherName = metadataFetcherName;
}
}

View File

@ -0,0 +1,38 @@
using System.Diagnostics.CodeAnalysis;
using API.Schema.Contexts;
using Microsoft.EntityFrameworkCore;
namespace API.Schema.MetadataFetchers;
[PrimaryKey("MetadataFetcherName")]
public abstract class MetadataFetcher
{
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
public string MetadataFetcherName { get; init; }
protected MetadataFetcher()
{
this.MetadataFetcherName = this.GetType().Name;
}
/// <summary>
/// EFCORE ONLY!!!
/// </summary>
internal MetadataFetcher(string metadataFetcherName)
{
this.MetadataFetcherName = metadataFetcherName;
}
public abstract MetadataEntry? FindLinkedMetadataEntry(Manga manga);
public bool TryGetMetadataEntry(Manga manga, [NotNullWhen(true)] out MetadataEntry? metadataEntry)
{
metadataEntry = FindLinkedMetadataEntry(manga);
return metadataEntry != null;
}
/// <summary>
/// Updates the Manga linked in the MetadataEntry
/// </summary>
public abstract void UpdateMetadata(MetadataEntry metadataEntry, PgsqlContext dbContext);
}

View File

@ -0,0 +1,70 @@
using System.Text.RegularExpressions;
using API.Schema.Contexts;
using JikanDotNet;
using Microsoft.EntityFrameworkCore;
namespace API.Schema.MetadataFetchers;
public class MyAnimeList : MetadataFetcher
{
private static readonly Jikan Jikan = new ();
private static readonly Regex GetIdFromUrl = new(@"https?:\/\/myanimelist\.net\/manga\/([0-9]+)\/?.*");
public override MetadataEntry? FindLinkedMetadataEntry(Manga manga)
{
if (manga.Links.Any(link => link.LinkProvider.Equals("MyAnimeList", StringComparison.InvariantCultureIgnoreCase)))
{
string url = manga.Links.First(link => link.LinkProvider.Equals("MyAnimeList", StringComparison.InvariantCultureIgnoreCase)).LinkUrl;
Match m = GetIdFromUrl.Match(url);
if (m.Success && m.Groups[1].Success)
{
long id = long.Parse(m.Groups[1].Value);
return new MetadataEntry(this, manga, id.ToString()!);
}
}
ICollection<JikanDotNet.Manga> resultData = Jikan.SearchMangaAsync(manga.Name).Result.Data;
if (resultData.Count < 1)
return null;
return new MetadataEntry(this, manga, resultData.First().MalId.ToString());
}
/// <summary>
/// Updates the Manga linked in the MetadataEntry
/// </summary>
/// <param name="metadataEntry"></param>
/// <param name="dbContext"></param>
/// <exception cref="FormatException"></exception>
/// <exception cref="DbUpdateException"></exception>
public override void UpdateMetadata(MetadataEntry metadataEntry, PgsqlContext dbContext)
{
Manga dbManga = dbContext.Mangas.Find(metadataEntry.MangaId)!;
MangaFull resultData;
try
{
long id = long.Parse(metadataEntry.Identifier);
resultData = Jikan.GetMangaFullDataAsync(id).Result.Data;
}
catch (Exception)
{
throw new FormatException("ID was not in correct format");
}
try
{
dbManga.Name = resultData.Titles.First().Title;
dbManga.Description = resultData.Synopsis;
dbManga.AltTitles.Clear();
dbManga.AltTitles = resultData.Titles.Select(t => new MangaAltTitle(t.Type, t.Title)).ToList();
dbManga.Authors.Clear();
dbManga.Authors = resultData.Authors.Select(a => new Author(a.Name)).ToList();
dbContext.SaveChanges();
}
catch (DbUpdateException e)
{
throw;
}
}
}