Skip to content

Commit

Permalink
Add Comic Vine Person provider (#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pithaya authored Jun 28, 2024
1 parent ee331c1 commit f6cf809
Show file tree
Hide file tree
Showing 17 changed files with 1,000 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ protected BaseComicVineProvider(ILogger<BaseComicVineProvider> logger, IComicVin
{
Type issue when issue == typeof(IssueDetails) => ComicVineApiUrls.IssueDetailUrl,
Type volume when volume == typeof(VolumeDetails) => ComicVineApiUrls.VolumeDetailUrl,
Type person when person == typeof(PersonDetails) => ComicVineApiUrls.PersonDetailUrl,
_ => throw new InvalidOperationException($"Unexpected resource type {typeof(T)}.")
};

Expand Down Expand Up @@ -222,5 +223,71 @@ protected IReadOnlyList<T> GetFromApiResponse<T>(BaseApiResponse<T> response)

return null;
}

/// <summary>
/// Gets images URLs from a list of images.
/// </summary>
/// <param name="imageList">The list of images.</param>
/// <returns>The list of images URLs.</returns>
protected IEnumerable<string> ProcessImages(ImageList? imageList)
{
if (imageList == null)
{
yield break;
}

if (!string.IsNullOrWhiteSpace(imageList.SuperUrl))
{
yield return imageList.SuperUrl;
}
else if (!string.IsNullOrWhiteSpace(imageList.OriginalUrl))
{
yield return imageList.OriginalUrl;
}
else if (!string.IsNullOrWhiteSpace(imageList.MediumUrl))
{
yield return imageList.MediumUrl;
}
else if (!string.IsNullOrWhiteSpace(imageList.SmallUrl))
{
yield return imageList.SmallUrl;
}
else if (!string.IsNullOrWhiteSpace(imageList.ThumbUrl))
{
yield return imageList.ThumbUrl;
}
}

/// <summary>
/// Gets the issue id from the site detail URL.
/// <para>
/// Issues have a unique Id, but also a different one used for the API.
/// The URL to the issue detail page also includes a slug before the id.
/// </para>
/// <listheader>For example:</listheader>
/// <list type="bullet">
/// <item>
/// <term>id</term>
/// <description>441467</description>
/// </item>
/// <item>
/// <term>api_detail_url</term>
/// <description>https://comicvine.gamespot.com/api/issue/4000-441467</description>
/// </item>
/// <item>
/// <term>site_detail_url</term>
/// <description>https://comicvine.gamespot.com/attack-on-titan-10-fortress-of-blood/4000-441467</description>
/// </item>
/// </list>
/// <para>
/// We need to keep the last two parts of the site detail URL (the slug and the id) as the provider id for the IExternalId implementation to work.
/// </para>
/// </summary>
/// <param name="siteDetailUrl">The site detail URL.</param>
/// <returns>The slug and id.</returns>
protected static string GetProviderIdFromSiteDetailUrl(string siteDetailUrl)
{
return siteDetailUrl.Replace(ComicVineApiUrls.BaseWebsiteUrl, string.Empty, StringComparison.OrdinalIgnoreCase).Trim('/');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ public class ComicVineImageProvider : BaseComicVineProvider, IRemoteImageProvide
/// <param name="logger">Instance of the <see cref="ILogger{ComicVineImageProvider}"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="apiKeyProvider">Instance of the <see cref="IComicVineApiKeyProvider"/> interface.</param>
public ComicVineImageProvider(IComicVineMetadataCacheManager comicVineMetadataCacheManager, ILogger<ComicVineImageProvider> logger, IHttpClientFactory httpClientFactory, IComicVineApiKeyProvider apiKeyProvider)
public ComicVineImageProvider(
IComicVineMetadataCacheManager comicVineMetadataCacheManager,
ILogger<ComicVineImageProvider> logger,
IHttpClientFactory httpClientFactory,
IComicVineApiKeyProvider apiKeyProvider)
: base(logger, comicVineMetadataCacheManager, httpClientFactory, apiKeyProvider)
{
_logger = logger;
Expand Down Expand Up @@ -68,7 +72,7 @@ public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, Cancell
return Enumerable.Empty<RemoteImageInfo>();
}

var images = ProcessIssueImages(issueDetails)
var images = ProcessImages(issueDetails.Image)
.Select(url => new RemoteImageInfo
{
Url = url,
Expand All @@ -78,44 +82,6 @@ public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, Cancell
return images;
}

/// <summary>
/// Gets images URLs from the issue.
/// </summary>
/// <param name="issueDetails">The issue details.</param>
/// <returns>The list of images URLs.</returns>
private IEnumerable<string> ProcessIssueImages(IssueDetails issueDetails)
{
if (issueDetails.Image == null)
{
return Enumerable.Empty<string>();
}

var images = new List<string>();

if (!string.IsNullOrWhiteSpace(issueDetails.Image.SuperUrl))
{
images.Add(issueDetails.Image.SuperUrl);
}
else if (!string.IsNullOrWhiteSpace(issueDetails.Image.OriginalUrl))
{
images.Add(issueDetails.Image.OriginalUrl);
}
else if (!string.IsNullOrWhiteSpace(issueDetails.Image.MediumUrl))
{
images.Add(issueDetails.Image.MediumUrl);
}
else if (!string.IsNullOrWhiteSpace(issueDetails.Image.SmallUrl))
{
images.Add(issueDetails.Image.SmallUrl);
}
else if (!string.IsNullOrWhiteSpace(issueDetails.Image.ThumbUrl))
{
images.Add(issueDetails.Image.ThumbUrl);
}

return images;
}

/// <inheritdoc/>
public async Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ public class ComicVineMetadataProvider : BaseComicVineProvider, IRemoteMetadataP
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="comicVineMetadataCacheManager">Instance of the <see cref="IComicVineMetadataCacheManager"/> interface.</param>
/// <param name="apiKeyProvider">Instance of the <see cref="IComicVineApiKeyProvider"/> interface.</param>
public ComicVineMetadataProvider(ILogger<ComicVineMetadataProvider> logger, IHttpClientFactory httpClientFactory, IComicVineMetadataCacheManager comicVineMetadataCacheManager, IComicVineApiKeyProvider apiKeyProvider)
public ComicVineMetadataProvider(
ILogger<ComicVineMetadataProvider> logger,
IHttpClientFactory httpClientFactory,
IComicVineMetadataCacheManager comicVineMetadataCacheManager,
IComicVineApiKeyProvider apiKeyProvider)
: base(logger, comicVineMetadataCacheManager, httpClientFactory, apiKeyProvider)
{
_logger = logger;
Expand Down Expand Up @@ -362,37 +366,5 @@ internal string GetSearchString(BookInfo item)

return result.Trim();
}

/// <summary>
/// Gets the issue id from the site detail URL.
/// <para>
/// Issues have a unique Id, but also a different one used for the API.
/// The URL to the issue detail page also includes a slug before the id.
/// </para>
/// <listheader>For example:</listheader>
/// <list type="bullet">
/// <item>
/// <term>id</term>
/// <description>441467</description>
/// </item>
/// <item>
/// <term>api_detail_url</term>
/// <description>https://comicvine.gamespot.com/api/issue/4000-441467</description>
/// </item>
/// <item>
/// <term>site_detail_url</term>
/// <description>https://comicvine.gamespot.com/attack-on-titan-10-fortress-of-blood/4000-441467</description>
/// </item>
/// </list>
/// <para>
/// We need to keep the last two parts of the site detail URL (the slug and the id) as the provider id for the IExternalId implementation to work.
/// </para>
/// </summary>
/// <param name="siteDetailUrl">The site detail URL.</param>
/// <returns>The slug and id.</returns>
private static string GetProviderIdFromSiteDetailUrl(string siteDetailUrl)
{
return siteDetailUrl.Replace(ComicVineApiUrls.BaseWebsiteUrl, string.Empty, StringComparison.OrdinalIgnoreCase).Trim('/');
}
}
}
10 changes: 10 additions & 0 deletions Jellyfin.Plugin.Bookshelf/Providers/ComicVine/ComicVineApiUrls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,15 @@ internal static class ComicVineApiUrls
/// Gets the URL used to fetch a specific volume.
/// </summary>
public const string VolumeDetailUrl = BaseUrl + @"/volume/{1}?api_key={0}&format=json&field_list=api_detail_url,id,name,site_detail_url,count_of_issues,description,publisher";

/// <summary>
/// Gets the URL used to search for persons.
/// </summary>
public const string PersonSearchUrl = BaseUrl + @"/search?api_key={0}&format=json&resources=person&query={1}";

/// <summary>
/// Gets the URL used to fetch a specific person.
/// </summary>
public const string PersonDetailUrl = BaseUrl + @"/person/{1}?api_key={0}&format=json&field_list=api_detail_url,id,name,site_detail_url,aliases,birth,country,death,deck,description,email,gender,hometown,image,website";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ public class PersonCredit
/// <summary>
/// Gets the list of roles for this person.
/// </summary>
public IEnumerable<PersonCreditRole> Roles => Role.Split(", ").Select(r => Enum.Parse<PersonCreditRole>(r, true));
public IEnumerable<PersonCreditRole> Roles
{
get
{
if (string.IsNullOrWhiteSpace(Role))
{
return Enumerable.Empty<PersonCreditRole>();
}

return Role.Split(", ", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(r => Enum.Parse<PersonCreditRole>(r, true));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Globalization;
using System.Text.Json.Serialization;

namespace Jellyfin.Plugin.Bookshelf.Providers.ComicVine.Models
{
/// <summary>
/// Details of a person.
/// </summary>
public class PersonDetails : PersonCredit
{
/// <summary>
/// Gets the list of aliases the person is known by. A \n (newline) seperates each alias.
/// </summary>
public string? Aliases { get; init; }

/// <summary>
/// Gets a date, if one exists, that the person was born on. Not an origin date.
/// </summary>
public string? Birth { get; init; }

/// <summary>
/// Gets a date, if one exists, that the person was born on. Not an origin date.
/// </summary>
[JsonIgnore]
public DateTime? BirthDate => ParseAsUTC(Birth);

/// <summary>
/// Gets the country the person resides in.
/// </summary>
public string? Country { get; init; }

/// <summary>
/// Gets the date this person died on.
/// </summary>
public string? Death { get; init; }

/// <summary>
/// Gets the date this person died on.
/// </summary>
[JsonIgnore]
public DateTime? DeathDate => ParseAsUTC(Death);

/// <summary>
/// Gets a brief summary of the person.
/// </summary>
public string? Deck { get; init; }

/// <summary>
/// Gets the description of the person.
/// </summary>
public string? Description { get; init; }

/// <summary>
/// Gets the email of this person.
/// </summary>
public string? Email { get; init; }

/// <summary>
/// Gets the gender of the person. Available options are: Male (1), Female (2), Other (0).
/// </summary>
public int? Gender { get; init; }

/// <summary>
/// Gets the city or town the person resides in.
/// </summary>
public string? Hometown { get; init; }

/// <summary>
/// Gets the main image of the person.
/// </summary>
public ImageList? Image { get; init; }

/// <summary>
/// Gets the URL to the person website.
/// </summary>
public string? Website { get; init; }

private static DateTime? ParseAsUTC(string? value)
{
if (!string.IsNullOrWhiteSpace(value) && DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime result))
{
return result;
}

return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;

namespace Jellyfin.Plugin.Bookshelf.Providers.ComicVine
{
/// <inheritdoc />
public class ComicVinePersonExternalId : IExternalId
{
/// <inheritdoc />
public string ProviderName => ComicVineConstants.ProviderName;

/// <inheritdoc />
public string Key => ComicVineConstants.ProviderId;

/// <inheritdoc />
public ExternalIdMediaType? Type => ExternalIdMediaType.Person;

/// <inheritdoc />
public string? UrlFormatString => ComicVineApiUrls.BaseWebsiteUrl + "/{0}";

/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Person;
}
}
Loading

0 comments on commit f6cf809

Please sign in to comment.