Skip to content

Commit 952110e

Browse files
committed
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.
1 parent 76c2055 commit 952110e

File tree

2 files changed

+160
-0
lines changed

2 files changed

+160
-0
lines changed

Shoko.Server/API/v3/Controllers/ImageController.cs

+42
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,48 @@ public ActionResult GetRandomImageForType([FromRoute] Image.ImageType imageType)
113113
return File(System.IO.File.OpenRead(path), Mime.GetMimeMapping(path));
114114
}
115115

116+
/// <summary>
117+
/// Returns the metadata for a random image for the <paramref name="imageType"/>.
118+
/// </summary>
119+
/// <param name="imageType">Poster, Fanart, Banner, Thumb</param>
120+
/// <returns>200 on found, 400 if the type or source are invalid</returns>
121+
[HttpGet("Random/{imageType}/Metadata")]
122+
[ProducesResponseType(typeof(Image), 200)]
123+
[ProducesResponseType(400)]
124+
[ProducesResponseType(500)]
125+
public ActionResult<Image> GetRandomImageMetadataForType([FromRoute] Image.ImageType imageType)
126+
{
127+
if (imageType == Image.ImageType.Static)
128+
return BadRequest("Unsupported image type for random image.");
129+
130+
var source = Image.GetRandomImageSource(imageType);
131+
var sourceType = Image.GetImageTypeFromSourceAndType(source, imageType) ?? ImageEntityType.None;
132+
if (sourceType == ImageEntityType.None)
133+
return InternalError("Could not generate a valid image type to fetch.");
134+
135+
// Try 5 times to get a valid image.
136+
var tries = 0;
137+
do
138+
{
139+
var id = Image.GetRandomImageID(sourceType);
140+
if (!id.HasValue)
141+
break;
142+
143+
var path = Image.GetImagePath(sourceType, id.Value);
144+
if (string.IsNullOrEmpty(path))
145+
continue;
146+
147+
var image = new Image(id.Value, sourceType, false, false);
148+
var series = Image.GetFirstSeriesForImage(sourceType, id.Value);
149+
if (series != null)
150+
image.Series = new(series.AnimeSeriesID, series.GetSeriesName());
151+
152+
return image;
153+
} while (tries++ < 5);
154+
155+
return InternalError("Unable to find a random image to send.");
156+
}
157+
116158
public ImageController(ISettingsProvider settingsProvider) : base(settingsProvider)
117159
{
118160
}

Shoko.Server/API/v3/Models/Common/Image.cs

+118
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Shoko.Models.Enums;
1111
using Shoko.Server.Extensions;
1212
using Shoko.Server.ImageDownload;
13+
using Shoko.Server.Models;
1314
using Shoko.Server.Repositories;
1415
using Shoko.Server.Utilities;
1516

@@ -65,6 +66,13 @@ public class Image
6566
/// </summary>
6667
public bool Disabled { get; set; }
6768

69+
/// <summary>
70+
/// Series info for the image, currently only set when sending a random
71+
/// image.
72+
/// </summary>
73+
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
74+
public ImageSeriesInfo? Series { get; set; } = null;
75+
6876
public Image(int id, ImageEntityType type, bool preferred = false, bool disabled = false) : this(id.ToString(),
6977
type, preferred, disabled)
7078
{
@@ -537,6 +545,97 @@ internal static ImageSource GetRandomImageSource(ImageType imageType)
537545
};
538546
}
539547

548+
internal static SVR_AnimeSeries? GetFirstSeriesForImage(ImageEntityType imageType, int imageID)
549+
{
550+
switch (imageType)
551+
{
552+
case ImageEntityType.AniDB_Cover:
553+
return RepoFactory.AnimeSeries.GetByAnimeID(imageID);
554+
case ImageEntityType.TvDB_Banner:
555+
{
556+
var tvdbWideBanner = RepoFactory.TvDB_ImageWideBanner.GetByID(imageID);
557+
if (tvdbWideBanner == null)
558+
return null;
559+
560+
var xref = RepoFactory.CrossRef_AniDB_TvDB.GetByTvDBID(tvdbWideBanner.SeriesID)
561+
.FirstOrDefault();
562+
if (xref == null)
563+
return null;
564+
565+
return RepoFactory.AnimeSeries.GetByAnimeID(xref.AniDBID);
566+
}
567+
case ImageEntityType.TvDB_Cover:
568+
{
569+
var tvdbPoster = RepoFactory.TvDB_ImagePoster.GetByID(imageID);
570+
if (tvdbPoster == null)
571+
return null;
572+
573+
var xref = RepoFactory.CrossRef_AniDB_TvDB.GetByTvDBID(tvdbPoster.SeriesID)
574+
.FirstOrDefault();
575+
if (xref == null)
576+
return null;
577+
578+
return RepoFactory.AnimeSeries.GetByAnimeID(xref.AniDBID);
579+
}
580+
case ImageEntityType.TvDB_FanArt:
581+
{
582+
var tvdbFanart = RepoFactory.TvDB_ImageFanart.GetByID(imageID);
583+
if (tvdbFanart == null)
584+
return null;
585+
586+
var xref = RepoFactory.CrossRef_AniDB_TvDB.GetByTvDBID(tvdbFanart.SeriesID)
587+
.FirstOrDefault();
588+
if (xref == null)
589+
return null;
590+
591+
return RepoFactory.AnimeSeries.GetByAnimeID(xref.AniDBID);
592+
}
593+
case ImageEntityType.TvDB_Episode:
594+
{
595+
var tvdbEpisode = RepoFactory.TvDB_Episode.GetByID(imageID);
596+
if (tvdbEpisode == null)
597+
return null;
598+
599+
var xref = RepoFactory.CrossRef_AniDB_TvDB.GetByTvDBID(tvdbEpisode.SeriesID)
600+
.FirstOrDefault();
601+
if (xref == null)
602+
return null;
603+
604+
return RepoFactory.AnimeSeries.GetByAnimeID(xref.AniDBID);
605+
}
606+
case ImageEntityType.MovieDB_FanArt:
607+
{
608+
var tmdbFanart = RepoFactory.MovieDB_Fanart.GetByID(imageID);
609+
if (tmdbFanart == null)
610+
return null;
611+
612+
// This will be slow as HELL. Why don't we have a lookup?
613+
var xref = RepoFactory.CrossRef_AniDB_Other.GetAll()
614+
.FirstOrDefault(xref => xref.CrossRefType == (int)CrossRefType.MovieDB && int.Parse(xref.CrossRefID) == tmdbFanart.MovieId);
615+
if (xref == null)
616+
return null;
617+
618+
return RepoFactory.AnimeSeries.GetByAnimeID(xref.AnimeID);
619+
}
620+
case ImageEntityType.MovieDB_Poster:
621+
{
622+
var tmdbPoster = RepoFactory.MovieDB_Poster.GetByID(imageID);
623+
if (tmdbPoster == null)
624+
return null;
625+
626+
// This will be slow as HELL. Why don't we have a lookup?
627+
var xref = RepoFactory.CrossRef_AniDB_Other.GetAll()
628+
.FirstOrDefault(xref => xref.CrossRefType == (int)CrossRefType.MovieDB && int.Parse(xref.CrossRefID) == tmdbPoster.MovieId);
629+
if (xref == null)
630+
return null;
631+
632+
return RepoFactory.AnimeSeries.GetByAnimeID(xref.AnimeID);
633+
}
634+
default:
635+
return null;
636+
};
637+
}
638+
540639
/// <summary>
541640
/// Image source.
542641
/// </summary>
@@ -606,6 +705,25 @@ public enum ImageType
606705
Static = 100
607706
}
608707

708+
public class ImageSeriesInfo
709+
{
710+
/// <summary>
711+
/// The shoko series id.
712+
/// </summary>
713+
public int ID { get; set; }
714+
715+
/// <summary>
716+
/// The preferred series name for the user.
717+
/// </summary>
718+
public string Name { get; set; }
719+
720+
public ImageSeriesInfo(int id, string name)
721+
{
722+
ID = id;
723+
Name = name;
724+
}
725+
}
726+
609727
/// <summary>
610728
/// Input models.
611729
/// </summary>

0 commit comments

Comments
 (0)