From 952110e204772e1a03652ceb42177d82cfff5ea6 Mon Sep 17 00:00:00 2001 From: Mikal Stordal Date: Thu, 6 Apr 2023 18:44:15 +0200 Subject: [PATCH] feat: add random image metadata endpoint add an endpoint to get the metadata for a random image, instead of the image itself, also with some basic series metadata, if appropriate and available. this endpoint was needed to show the series name on the login screen, but it can be used for other things too. --- .../API/v3/Controllers/ImageController.cs | 42 +++++++ Shoko.Server/API/v3/Models/Common/Image.cs | 118 ++++++++++++++++++ 2 files changed, 160 insertions(+) diff --git a/Shoko.Server/API/v3/Controllers/ImageController.cs b/Shoko.Server/API/v3/Controllers/ImageController.cs index 4969c0a08..095f4c137 100644 --- a/Shoko.Server/API/v3/Controllers/ImageController.cs +++ b/Shoko.Server/API/v3/Controllers/ImageController.cs @@ -113,6 +113,48 @@ public ActionResult GetRandomImageForType([FromRoute] Image.ImageType imageType) return File(System.IO.File.OpenRead(path), Mime.GetMimeMapping(path)); } + /// + /// Returns the metadata for a random image for the . + /// + /// Poster, Fanart, Banner, Thumb + /// 200 on found, 400 if the type or source are invalid + [HttpGet("Random/{imageType}/Metadata")] + [ProducesResponseType(typeof(Image), 200)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public ActionResult GetRandomImageMetadataForType([FromRoute] Image.ImageType imageType) + { + if (imageType == Image.ImageType.Static) + return BadRequest("Unsupported image type for random image."); + + var source = Image.GetRandomImageSource(imageType); + var sourceType = Image.GetImageTypeFromSourceAndType(source, imageType) ?? ImageEntityType.None; + if (sourceType == ImageEntityType.None) + return InternalError("Could not generate a valid image type to fetch."); + + // Try 5 times to get a valid image. + var tries = 0; + do + { + var id = Image.GetRandomImageID(sourceType); + if (!id.HasValue) + break; + + var path = Image.GetImagePath(sourceType, id.Value); + if (string.IsNullOrEmpty(path)) + continue; + + var image = new Image(id.Value, sourceType, false, false); + var series = Image.GetFirstSeriesForImage(sourceType, id.Value); + if (series != null) + image.Series = new(series.AnimeSeriesID, series.GetSeriesName()); + + return image; + } while (tries++ < 5); + + return InternalError("Unable to find a random image to send."); + } + public ImageController(ISettingsProvider settingsProvider) : base(settingsProvider) { } diff --git a/Shoko.Server/API/v3/Models/Common/Image.cs b/Shoko.Server/API/v3/Models/Common/Image.cs index c37a74151..88b200a89 100644 --- a/Shoko.Server/API/v3/Models/Common/Image.cs +++ b/Shoko.Server/API/v3/Models/Common/Image.cs @@ -10,6 +10,7 @@ using Shoko.Models.Enums; using Shoko.Server.Extensions; using Shoko.Server.ImageDownload; +using Shoko.Server.Models; using Shoko.Server.Repositories; using Shoko.Server.Utilities; @@ -65,6 +66,13 @@ public class Image /// public bool Disabled { get; set; } + /// + /// Series info for the image, currently only set when sending a random + /// image. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public ImageSeriesInfo? Series { get; set; } = null; + public Image(int id, ImageEntityType type, bool preferred = false, bool disabled = false) : this(id.ToString(), type, preferred, disabled) { @@ -537,6 +545,97 @@ internal static ImageSource GetRandomImageSource(ImageType imageType) }; } + internal static SVR_AnimeSeries? GetFirstSeriesForImage(ImageEntityType imageType, int imageID) + { + switch (imageType) + { + case ImageEntityType.AniDB_Cover: + return RepoFactory.AnimeSeries.GetByAnimeID(imageID); + case ImageEntityType.TvDB_Banner: + { + var tvdbWideBanner = RepoFactory.TvDB_ImageWideBanner.GetByID(imageID); + if (tvdbWideBanner == null) + return null; + + var xref = RepoFactory.CrossRef_AniDB_TvDB.GetByTvDBID(tvdbWideBanner.SeriesID) + .FirstOrDefault(); + if (xref == null) + return null; + + return RepoFactory.AnimeSeries.GetByAnimeID(xref.AniDBID); + } + case ImageEntityType.TvDB_Cover: + { + var tvdbPoster = RepoFactory.TvDB_ImagePoster.GetByID(imageID); + if (tvdbPoster == null) + return null; + + var xref = RepoFactory.CrossRef_AniDB_TvDB.GetByTvDBID(tvdbPoster.SeriesID) + .FirstOrDefault(); + if (xref == null) + return null; + + return RepoFactory.AnimeSeries.GetByAnimeID(xref.AniDBID); + } + case ImageEntityType.TvDB_FanArt: + { + var tvdbFanart = RepoFactory.TvDB_ImageFanart.GetByID(imageID); + if (tvdbFanart == null) + return null; + + var xref = RepoFactory.CrossRef_AniDB_TvDB.GetByTvDBID(tvdbFanart.SeriesID) + .FirstOrDefault(); + if (xref == null) + return null; + + return RepoFactory.AnimeSeries.GetByAnimeID(xref.AniDBID); + } + case ImageEntityType.TvDB_Episode: + { + var tvdbEpisode = RepoFactory.TvDB_Episode.GetByID(imageID); + if (tvdbEpisode == null) + return null; + + var xref = RepoFactory.CrossRef_AniDB_TvDB.GetByTvDBID(tvdbEpisode.SeriesID) + .FirstOrDefault(); + if (xref == null) + return null; + + return RepoFactory.AnimeSeries.GetByAnimeID(xref.AniDBID); + } + case ImageEntityType.MovieDB_FanArt: + { + var tmdbFanart = RepoFactory.MovieDB_Fanart.GetByID(imageID); + if (tmdbFanart == null) + return null; + + // This will be slow as HELL. Why don't we have a lookup? + var xref = RepoFactory.CrossRef_AniDB_Other.GetAll() + .FirstOrDefault(xref => xref.CrossRefType == (int)CrossRefType.MovieDB && int.Parse(xref.CrossRefID) == tmdbFanart.MovieId); + if (xref == null) + return null; + + return RepoFactory.AnimeSeries.GetByAnimeID(xref.AnimeID); + } + case ImageEntityType.MovieDB_Poster: + { + var tmdbPoster = RepoFactory.MovieDB_Poster.GetByID(imageID); + if (tmdbPoster == null) + return null; + + // This will be slow as HELL. Why don't we have a lookup? + var xref = RepoFactory.CrossRef_AniDB_Other.GetAll() + .FirstOrDefault(xref => xref.CrossRefType == (int)CrossRefType.MovieDB && int.Parse(xref.CrossRefID) == tmdbPoster.MovieId); + if (xref == null) + return null; + + return RepoFactory.AnimeSeries.GetByAnimeID(xref.AnimeID); + } + default: + return null; + }; + } + /// /// Image source. /// @@ -606,6 +705,25 @@ public enum ImageType Static = 100 } + public class ImageSeriesInfo + { + /// + /// The shoko series id. + /// + public int ID { get; set; } + + /// + /// The preferred series name for the user. + /// + public string Name { get; set; } + + public ImageSeriesInfo(int id, string name) + { + ID = id; + Name = name; + } + } + /// /// Input models. ///