Skip to content

Commit

Permalink
Support for retreiving Person metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
HansLehnert committed Apr 14, 2024
1 parent ef3b959 commit ca548c2
Show file tree
Hide file tree
Showing 9 changed files with 384 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Jellyfin.Plugin.AniList.Providers.AniList
{
public class AniListExternalId : IExternalId
public class AniListAnimeExternalId : IExternalId
{
public bool Supports(IHasProviderIds item)
=> item is Series || item is Movie;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@

namespace Jellyfin.Plugin.AniList.Providers.AniList
{
public class AniListImageProvider : IRemoteImageProvider
public class AniListAnimeImageProvider : IRemoteImageProvider
{
private readonly AniListApi _aniListApi;
public AniListImageProvider()
public AniListAnimeImageProvider()
{
_aniListApi = new AniListApi();
}
Expand All @@ -44,7 +44,7 @@ public async Task<IEnumerable<RemoteImageInfo>> GetImages(string aid, Cancellati

if (!string.IsNullOrEmpty(aid))
{
Media media = await _aniListApi.GetAnime(aid);
Media media = await _aniListApi.GetAnime(aid, cancellationToken);
if (media != null)
{
if (media.GetImageUrl() != null)
Expand Down
162 changes: 126 additions & 36 deletions Jellyfin.Plugin.AniList/Providers/AniList/AniListApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,30 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.AniList.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using Jellyfin.Extensions;
using Microsoft.Extensions.Logging;

namespace Jellyfin.Plugin.AniList.Providers.AniList
{
/// <summary>
/// Based on the new API from AniList
/// 🛈 This code works with the API Interface (v2) from AniList
/// 🛈 https://anilist.gitbooks.io/anilist-apiv2-docs
/// 🛈 https://anilist.gitbook.io/anilist-apiv2-docs
/// 🛈 THIS IS AN UNOFFICAL API INTERFACE FOR JELLYFIN
/// </summary>
public class AniListApi
{
private const string SearchLink = @"https://graphql.anilist.co/api/v2?query=
query ($query: String, $type: MediaType) {
private const string BaseApiUrl = "https://graphql.anilist.co/";

private const string SearchAnimeGraphqlQuery = @"
query ($query: String) {
Page {
media(search: $query, type: $type) {
media(search: $query, type: ANIME) {
id
title {
romaji
Expand All @@ -42,10 +44,11 @@ public class AniListApi
}
}
}
}&variables={ ""query"":""{0}"",""type"":""ANIME""}";
public string AnimeLink = @"https://graphql.anilist.co/api/v2?query=
query($id: Int!, $type: MediaType) {
Media(id: $id, type: $type) {
}";

private const string GetAnimeGraphqlQuery = @"
query($id: Int!) {
Media(id: $id, type: ANIME) {
id
title {
romaji
Expand Down Expand Up @@ -102,7 +105,7 @@ public class AniListApi
isAnimationStudio
}
}
characters(sort: [ROLE]) {
characters(sort: [ROLE, FAVOURITES_DESC]) {
edges {
node {
id
Expand All @@ -129,25 +132,85 @@ public class AniListApi
medium
large
}
language
languageV2
}
}
}
}
}&variables={ ""id"":""{0}"",""type"":""ANIME""}";
}";

static AniListApi()
{
private const string SearchStaffGraphqlQuery = @"
query($query: String) {
Page {
staff(search: $query) {
id
name {
first
last
full
native
}
image {
large
medium
}
}
}
}";

private const string GetStaffGraphqlQuery = @"
query($id: Int!) {
Staff(id: $id) {
id
name {
first
last
full
native
}
image {
large
medium
}
description(asHtml: true)
homeTown
dateOfBirth {
year
month
day
}
dateOfDeath {
year
month
day
}
}
}";

private class GraphQlRequest {
[JsonPropertyName("query")]
public string Query { get; set; }

[JsonPropertyName("variables")]
public Dictionary<string, string> Variables { get; set; }
}

/// <summary>
/// API call to get the anime with the given id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<Media> GetAnime(string id)
public async Task<Media> GetAnime(string id, CancellationToken cancellationToken)
{
return (await WebRequestAPI(AnimeLink.Replace("{0}", id))).data?.Media;
RootObject result = await WebRequestAPI(
new GraphQlRequest {
Query = GetAnimeGraphqlQuery,
Variables = new Dictionary<string, string> {{"id", id}},
},
cancellationToken
);

return result.data?.Media;
}

/// <summary>
Expand All @@ -158,13 +221,7 @@ public async Task<Media> GetAnime(string id)
/// <returns></returns>
public async Task<MediaSearchResult> Search_GetSeries(string title, CancellationToken cancellationToken)
{
// Reimplemented instead of calling Search_GetSeries_list() for efficiency
RootObject WebContent = await WebRequestAPI(SearchLink.Replace("{0}", title));
foreach (MediaSearchResult media in WebContent.data.Page.media)
{
return media;
}
return null;
return (await Search_GetSeries_list(title, cancellationToken)).FirstOrDefault();
}

/// <summary>
Expand All @@ -175,7 +232,15 @@ public async Task<MediaSearchResult> Search_GetSeries(string title, Cancellation
/// <returns></returns>
public async Task<List<MediaSearchResult>> Search_GetSeries_list(string title, CancellationToken cancellationToken)
{
return (await WebRequestAPI(SearchLink.Replace("{0}", title))).data.Page.media;
RootObject result = await WebRequestAPI(
new GraphQlRequest {
Query = SearchAnimeGraphqlQuery,
Variables = new Dictionary<string, string> {{"query", title}},
},
cancellationToken
);

return result.data.Page.media;
}

/// <summary>
Expand All @@ -200,20 +265,45 @@ public async Task<string> FindSeries(string title, CancellationToken cancellatio
return null;
}

public async Task<Staff> GetStaff(int id, CancellationToken cancellationToken)
{
RootObject result = await WebRequestAPI(
new GraphQlRequest {
Query = GetStaffGraphqlQuery,
Variables = new Dictionary<string, string> {{"id", id.ToString()}},
},
cancellationToken
);

return result.data?.Staff;
}

public async Task<List<Staff>> SearchStaff(string query, CancellationToken cancellationToken)
{
RootObject result = await WebRequestAPI(
new GraphQlRequest {
Query = SearchStaffGraphqlQuery,
Variables = new Dictionary<string, string> {{"query", query}},
},
cancellationToken
);

return result.data?.Page.staff;
}

/// <summary>
/// GET and parse JSON content from link, deserialize into a RootObject
/// Send a GraphQL request, deserialize into a RootObject
/// </summary>
/// <param name="link"></param>
/// <param name="request">The GraphQl request payload</param>
/// <returns></returns>
public async Task<RootObject> WebRequestAPI(string link)
private async Task<RootObject> WebRequestAPI(GraphQlRequest request, CancellationToken cancellationToken)
{
var httpClient = Plugin.Instance.GetHttpClient();
using (HttpContent content = new FormUrlEncodedContent(Enumerable.Empty<KeyValuePair<string, string>>()))
using (var response = await httpClient.PostAsync(link, content).ConfigureAwait(false))
using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
return await JsonSerializer.DeserializeAsync<RootObject>(responseStream).ConfigureAwait(false);
}

using HttpContent content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
using var response = await httpClient.PostAsync(BaseApiUrl, content, cancellationToken).ConfigureAwait(false);
using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
return await JsonSerializer.DeserializeAsync<RootObject>(responseStream, cancellationToken: cancellationToken).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
Expand Down Expand Up @@ -42,7 +43,7 @@ public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, Cancellatio
var aid = info.ProviderIds.GetOrDefault(ProviderNames.AniList);
if (!string.IsNullOrEmpty(aid))
{
media = await _aniListApi.GetAnime(aid);
media = await _aniListApi.GetAnime(aid, cancellationToken);
}
else
{
Expand All @@ -56,7 +57,7 @@ public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, Cancellatio
msr = await _aniListApi.Search_GetSeries(searchName, cancellationToken);
if (msr != null)
{
media = await _aniListApi.GetAnime(msr.id.ToString());
media = await _aniListApi.GetAnime(msr.id.ToString(), cancellationToken);
}
}
if(!config.UseAnitomyLibrary || media == null)
Expand All @@ -67,7 +68,7 @@ public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, Cancellatio
msr = await _aniListApi.Search_GetSeries(searchName, cancellationToken);
if (msr != null)
{
media = await _aniListApi.GetAnime(msr.id.ToString());
media = await _aniListApi.GetAnime(msr.id.ToString(), cancellationToken);
}
}
}
Expand All @@ -90,7 +91,7 @@ public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo se
var aid = searchInfo.ProviderIds.GetOrDefault(ProviderNames.AniList);
if (!string.IsNullOrEmpty(aid))
{
Media aid_result = await _aniListApi.GetAnime(aid).ConfigureAwait(false);
Media aid_result = await _aniListApi.GetAnime(aid, cancellationToken).ConfigureAwait(false);
if (aid_result != null)
{
results.Add(aid_result.ToSearchResult());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;

namespace Jellyfin.Plugin.AniList.Providers.AniList
{
public class AniListPersonExternalId : IExternalId
{
public bool Supports(IHasProviderIds item)
=> item is Person;

public string ProviderName
=> "AniList";

public string Key
=> ProviderNames.AniList;

public ExternalIdMediaType? Type
=> ExternalIdMediaType.Person;

public string UrlFormatString
=> "https://anilist.co/staff/{0}/";
}
}
Loading

0 comments on commit ca548c2

Please sign in to comment.