mirror of
https://github.com/C9Glax/tranga.git
synced 2025-06-30 16:04:16 +02:00
Metadata-Site Search (Interactive linking)
This commit is contained in:
@ -4,6 +4,7 @@ using API.Schema.MetadataFetchers;
|
|||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using log4net;
|
using log4net;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
@ -38,45 +39,94 @@ public class MetadataFetcherController(PgsqlContext context, ILog Log) : Control
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries linking a Manga to a Metadata-Provider-Site
|
/// Searches Metadata-Provider for Manga-Metadata
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="searchTerm">Instead of using the Manga for search, use a specific term</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="400">Metadata-fetcher with Name does not exist</response>
|
/// <response code="400">Metadata-fetcher with Name does not exist</response>
|
||||||
/// <response code="404">Manga with ID not found</response>
|
/// <response code="404">Manga with ID not found</response>
|
||||||
/// <response code="417">Could not find Entry on Metadata-Provider for Manga</response>
|
[HttpPost("{MetadataFetcherName}/SearchManga/{MangaId}")]
|
||||||
/// <response code="500">Error during Database Operation</response>
|
[ProducesResponseType<MetadataSearchResult[]>(Status200OK, "application/json")]
|
||||||
[HttpPost("{MetadataFetcherName}/{MangaId}/TryLink")]
|
|
||||||
[ProducesResponseType<MetadataEntry>(Status200OK, "application/json")]
|
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType(Status417ExpectationFailed)]
|
public IActionResult SearchMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]string? searchTerm = null)
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
|
||||||
public IActionResult LinkMangaToMetadataFetcher(string MangaId, string MetadataFetcherName)
|
|
||||||
{
|
{
|
||||||
if(context.Mangas.Find(MangaId) is not { } manga)
|
if(context.Mangas.Find(MangaId) is not { } manga)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.MetadataFetcherName == MetadataFetcherName) is not { } fetcher)
|
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.MetadataFetcherName == MetadataFetcherName) is not { } fetcher)
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
if (!fetcher.TryGetMetadataEntry(manga, out MetadataEntry? entry))
|
|
||||||
{
|
MetadataSearchResult[] searchResults = searchTerm is null ? fetcher.SearchMetadataEntry(manga) : fetcher.SearchMetadataEntry(searchTerm);
|
||||||
return StatusCode(Status417ExpectationFailed, "Metadata entry not found");
|
return Ok(searchResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Links Metadata-Provider using Provider-Specific Identifier to Manga
|
||||||
|
/// </summary>
|
||||||
|
/// <response code="200"></response>
|
||||||
|
/// <response code="400">Metadata-fetcher with Name does not exist</response>
|
||||||
|
/// <response code="404">Manga with ID not found</response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
|
[HttpPost("{MetadataFetcherName}/Link/{MangaId}")]
|
||||||
|
[ProducesResponseType(Status200OK)]
|
||||||
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
|
[ProducesResponseType(Status404NotFound)]
|
||||||
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
|
public IActionResult LinkMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody]string Identifier)
|
||||||
|
{
|
||||||
|
if(context.Mangas.Find(MangaId) is not { } manga)
|
||||||
|
return NotFound();
|
||||||
|
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.MetadataFetcherName == MetadataFetcherName) is not { } fetcher)
|
||||||
|
return BadRequest();
|
||||||
|
MetadataEntry entry = fetcher.CreateMetadataEntry(manga, Identifier);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//Unlink previous metadata-entries
|
|
||||||
IQueryable<MetadataEntry> metadataEntries = context.MetadataEntries.Where(e => e.MangaId == MangaId && e.MetadataFetcherName == MetadataFetcherName);
|
|
||||||
context.MetadataEntries.RemoveRange(metadataEntries);
|
|
||||||
//Add new metadata-entry
|
|
||||||
context.MetadataEntries.Add(entry);
|
context.MetadataEntries.Add(entry);
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
return Ok(entry);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Error(e);
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Un-Links Metadata-Provider using Provider-Specific Identifier to Manga
|
||||||
|
/// </summary>
|
||||||
|
/// <response code="200"></response>
|
||||||
|
/// <response code="400">Metadata-fetcher with Name does not exist</response>
|
||||||
|
/// <response code="404">Manga with ID not found</response>
|
||||||
|
/// <response code="412">No Entry linking Manga and Metadata-Provider found</response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
|
[HttpPost("{MetadataFetcherName}/Unlink/{MangaId}")]
|
||||||
|
[ProducesResponseType(Status200OK)]
|
||||||
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
|
[ProducesResponseType(Status404NotFound)]
|
||||||
|
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
||||||
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
|
public IActionResult UnlinkMangaMetadata(string MangaId, string MetadataFetcherName)
|
||||||
|
{
|
||||||
|
if(context.Mangas.Find(MangaId) is not { } manga)
|
||||||
|
return NotFound();
|
||||||
|
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.MetadataFetcherName == MetadataFetcherName) is not { } fetcher)
|
||||||
|
return BadRequest();
|
||||||
|
MetadataEntry? entry = context.MetadataEntries.FirstOrDefault(e => e.MangaId == MangaId && e.MetadataFetcherName == MetadataFetcherName);
|
||||||
|
if (entry is null)
|
||||||
|
return StatusCode(Status412PreconditionFailed, "No entry found");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
context.MetadataEntries.Remove(entry);
|
||||||
|
context.SaveChanges();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e);
|
||||||
|
return StatusCode(500, e.Message);
|
||||||
|
}
|
||||||
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -23,13 +23,12 @@ public abstract class MetadataFetcher
|
|||||||
this.MetadataFetcherName = metadataFetcherName;
|
this.MetadataFetcherName = metadataFetcherName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract MetadataEntry? FindLinkedMetadataEntry(Manga manga);
|
internal MetadataEntry CreateMetadataEntry(Manga manga, string identifier) =>
|
||||||
|
new (this, manga, identifier);
|
||||||
|
|
||||||
public bool TryGetMetadataEntry(Manga manga, [NotNullWhen(true)] out MetadataEntry? metadataEntry)
|
public abstract MetadataSearchResult[] SearchMetadataEntry(Manga manga);
|
||||||
{
|
|
||||||
metadataEntry = FindLinkedMetadataEntry(manga);
|
public abstract MetadataSearchResult[] SearchMetadataEntry(string searchTerm);
|
||||||
return metadataEntry != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the Manga linked in the MetadataEntry
|
/// Updates the Manga linked in the MetadataEntry
|
||||||
|
3
API/Schema/MetadataFetchers/MetadataSearchResult.cs
Normal file
3
API/Schema/MetadataFetchers/MetadataSearchResult.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace API.Schema.MetadataFetchers;
|
||||||
|
|
||||||
|
public record MetadataSearchResult(string Identifier, string Name, string Url, string? Description = null, string? CoverUrl = null);
|
@ -10,7 +10,7 @@ public class MyAnimeList : MetadataFetcher
|
|||||||
private static readonly Jikan Jikan = new ();
|
private static readonly Jikan Jikan = new ();
|
||||||
private static readonly Regex GetIdFromUrl = new(@"https?:\/\/myanimelist\.net\/manga\/([0-9]+)\/?.*");
|
private static readonly Regex GetIdFromUrl = new(@"https?:\/\/myanimelist\.net\/manga\/([0-9]+)\/?.*");
|
||||||
|
|
||||||
public override MetadataEntry? FindLinkedMetadataEntry(Manga manga)
|
public override MetadataSearchResult[] SearchMetadataEntry(Manga manga)
|
||||||
{
|
{
|
||||||
if (manga.Links.Any(link => link.LinkProvider.Equals("MyAnimeList", StringComparison.InvariantCultureIgnoreCase)))
|
if (manga.Links.Any(link => link.LinkProvider.Equals("MyAnimeList", StringComparison.InvariantCultureIgnoreCase)))
|
||||||
{
|
{
|
||||||
@ -19,14 +19,23 @@ public class MyAnimeList : MetadataFetcher
|
|||||||
if (m.Success && m.Groups[1].Success)
|
if (m.Success && m.Groups[1].Success)
|
||||||
{
|
{
|
||||||
long id = long.Parse(m.Groups[1].Value);
|
long id = long.Parse(m.Groups[1].Value);
|
||||||
return new MetadataEntry(this, manga, id.ToString()!);
|
JikanDotNet.Manga data = Jikan.GetMangaAsync(id).Result.Data;
|
||||||
|
return [new MetadataSearchResult(id.ToString(), data.Titles.First().Title, data.Url, data.Synopsis)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ICollection<JikanDotNet.Manga> resultData = Jikan.SearchMangaAsync(manga.Name).Result.Data;
|
return SearchMetadataEntry(manga.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MetadataSearchResult[] SearchMetadataEntry(string searchTerm)
|
||||||
|
{
|
||||||
|
|
||||||
|
ICollection<JikanDotNet.Manga> resultData = Jikan.SearchMangaAsync(searchTerm).Result.Data;
|
||||||
if (resultData.Count < 1)
|
if (resultData.Count < 1)
|
||||||
return null;
|
return [];
|
||||||
return new MetadataEntry(this, manga, resultData.First().MalId.ToString());
|
return resultData.Select(data =>
|
||||||
|
new MetadataSearchResult(data.MalId.ToString(), data.Titles.First().Title, data.Url, data.Synopsis))
|
||||||
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
Reference in New Issue
Block a user