mirror of
https://github.com/jellyfin/jellyfin-plugin-anisearch.git
synced 2024-11-27 08:00:31 +00:00
Merge remote-tracking branch 'upstream/master' into feature/KitsuIO
This commit is contained in:
commit
5aba3fab14
@ -73,11 +73,11 @@
|
||||
|
||||
$('#titleLanguage', page).val(config.TitlePreference).change();
|
||||
$('#chkMaxGenres', page).val(config.MaxGenres).change();
|
||||
$('#chkTidyGenres', page).checked = config.TidyGenreList;
|
||||
$('#chkAddAnimeGenre', page).checked = config.AddAnimeGenre;
|
||||
document.getElementById('chkTidyGenres').checked = config.TidyGenreList;
|
||||
document.getElementById('chkAddAnimeGenre').checked = config.AddAnimeGenre;
|
||||
$('#chkAniDbWaitTime', page).val(config.AniDbWaitTime).change();
|
||||
$('#chkAniDbOrderWithSeasons', page).checked = config.AniDbOrderWithSeasons;
|
||||
$('#chkAniDbReplaceGraves', page).checked = config.AniDbReplaceGraves;
|
||||
document.getElementById('chkAniDbOrderWithSeasons').checked = config.AniDbOrderWithSeasons;
|
||||
document.getElementById('chkAniDbReplaceGraves').checked = config.AniDbReplaceGraves;
|
||||
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
@ -91,11 +91,11 @@
|
||||
|
||||
config.TitlePreference = $('#titleLanguage', page).val();
|
||||
config.MaxGenres = $('#chkMaxGenres').val();
|
||||
config.TidyGenreList = $('#chkTidyGenres').checked;
|
||||
config.AddAnimeGenre = $('#chkAddAnimeGenre').checked;
|
||||
config.TidyGenreList = document.getElementById('chkTidyGenres').checked;
|
||||
config.AddAnimeGenre = document.getElementById('chkAddAnimeGenre').checked;
|
||||
config.AniDbWaitTime = $('#chkAniDbWaitTime').val();
|
||||
config.AniDbOrderWithSeasons = $('#chkAniDbOrderWithSeasons').checked;
|
||||
config.AniDbReplaceGraves = $('#chkAniDbReplaceGraves').checked;
|
||||
config.AniDbOrderWithSeasons = document.getElementById('chkAniDbOrderWithSeasons').checked;
|
||||
config.AniDbReplaceGraves = document.getElementById('chkAniDbReplaceGraves').checked;
|
||||
|
||||
ApiClient.updatePluginConfiguration(AnimeConfigurationPage.pluginUniqueId, config).then(function (result) {
|
||||
Dashboard.processPluginConfigurationUpdateResult(result);
|
||||
|
@ -3,12 +3,12 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<RootNamespace>Jellyfin.Plugin.Anime</RootNamespace>
|
||||
<AssemblyVersion>8.0.0.0</AssemblyVersion>
|
||||
<FileVersion>8.0.0.0</FileVersion>
|
||||
<AssemblyVersion>9.0.0.0</AssemblyVersion>
|
||||
<FileVersion>9.0.0.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jellyfin.Controller" Version="10.*" />
|
||||
<PackageReference Include="Jellyfin.Controller" Version="10.*-*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using Jellyfin.Plugin.Anime.Configuration;
|
||||
using Jellyfin.Plugin.Anime.Providers.AniDB.Identity;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Model.Plugins;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
@ -16,14 +15,15 @@ namespace Jellyfin.Plugin.Anime
|
||||
public Plugin(
|
||||
IApplicationPaths applicationPaths,
|
||||
IXmlSerializer xmlSerializer,
|
||||
ILogger logger)
|
||||
ILogger<AniDbTitleMatcher> matcherLogger,
|
||||
ILogger<AniDbTitleDownloader> downloaderLogger)
|
||||
: base(applicationPaths, xmlSerializer)
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
AniDbTitleMatcher.DefaultInstance = new AniDbTitleMatcher(
|
||||
logger,
|
||||
new AniDbTitleDownloader(logger, applicationPaths));
|
||||
matcherLogger,
|
||||
new AniDbTitleDownloader(downloaderLogger, applicationPaths));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -2,6 +2,7 @@
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace Jellyfin.Plugin.Anime.Providers.AniDB
|
||||
{
|
||||
@ -10,12 +11,15 @@ namespace Jellyfin.Plugin.Anime.Providers.AniDB
|
||||
public bool Supports(IHasProviderIds item)
|
||||
=> item is Series || item is Movie;
|
||||
|
||||
public string Name
|
||||
public string ProviderName
|
||||
=> "AniDB";
|
||||
|
||||
public string Key
|
||||
=> ProviderNames.AniDb;
|
||||
|
||||
public ExternalIdMediaType? Type
|
||||
=> null;
|
||||
|
||||
public string UrlFormatString
|
||||
=> "https://anidb.net/perl-bin/animedb.pl?show=anime&aid={0}";
|
||||
}
|
||||
|
@ -21,9 +21,9 @@ namespace Jellyfin.Plugin.Anime.Providers.AniDB.Identity
|
||||
private const string TitlesUrl = "https://anidb.net/api/anime-titles.xml.gz";
|
||||
|
||||
private static readonly HttpClient _httpClient;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<AniDbTitleDownloader> _logger;
|
||||
|
||||
public AniDbTitleDownloader(ILogger logger, IApplicationPaths applicationPaths)
|
||||
public AniDbTitleDownloader(ILogger<AniDbTitleDownloader> logger, IApplicationPaths applicationPaths)
|
||||
{
|
||||
_logger = logger;
|
||||
Paths = GetDataPath(applicationPaths);
|
||||
|
@ -37,8 +37,8 @@ namespace Jellyfin.Plugin.Anime.Providers.AniDB.Identity
|
||||
/// </summary>
|
||||
public static IAniDbTitleMatcher DefaultInstance { get; set; }
|
||||
|
||||
private readonly ILogger _logger;
|
||||
public readonly IAniDbTitleDownloader _downloader;
|
||||
private readonly ILogger<AniDbTitleMatcher> _logger;
|
||||
private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
|
||||
|
||||
public static Dictionary<string, TitleInfo> _titles;
|
||||
@ -48,7 +48,7 @@ namespace Jellyfin.Plugin.Anime.Providers.AniDB.Identity
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="downloader">The AniDB title downloader.</param>
|
||||
public AniDbTitleMatcher(ILogger logger, IAniDbTitleDownloader downloader)
|
||||
public AniDbTitleMatcher(ILogger<AniDbTitleMatcher> logger, IAniDbTitleDownloader downloader)
|
||||
{
|
||||
_logger = logger;
|
||||
_downloader = downloader;
|
||||
|
@ -13,14 +13,14 @@ namespace Jellyfin.Plugin.Anime.Providers.AniDB.Metadata
|
||||
public class AniDbMovieProvider : IRemoteMetadataProvider<Movie, MovieInfo>
|
||||
{
|
||||
private readonly AniDbSeriesProvider _seriesProvider;
|
||||
private readonly ILogger _log;
|
||||
private readonly ILogger<AniDbMovieProvider> _logger;
|
||||
|
||||
public string Name => "AniDB";
|
||||
|
||||
public AniDbMovieProvider(IApplicationPaths appPaths, IHttpClient httpClient, ILoggerFactory loggerFactory)
|
||||
public AniDbMovieProvider(IApplicationPaths appPaths, IHttpClient httpClient, ILogger<AniDbMovieProvider> logger)
|
||||
{
|
||||
_seriesProvider = new AniDbSeriesProvider(appPaths, httpClient);
|
||||
_log = loggerFactory.CreateLogger("AniDB");
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
|
||||
|
@ -16,7 +16,7 @@ namespace Jellyfin.Plugin.Anime.Providers.AniList
|
||||
/// Based on the new API from AniList
|
||||
/// 🛈 This code works with the API Interface (v2) from AniList
|
||||
/// 🛈 https://anilist.gitbooks.io/anilist-apiv2-docs
|
||||
/// 🛈 THIS IS AN UNOFFICAL API INTERFACE FOR EMBY
|
||||
/// 🛈 THIS IS AN UNOFFICAL API INTERFACE FOR JELLYFIN
|
||||
/// </summary>
|
||||
public class AniListApi
|
||||
{
|
||||
@ -34,89 +34,82 @@ query ($query: String, $type: MediaType) {
|
||||
coverImage {
|
||||
medium
|
||||
large
|
||||
extraLarge
|
||||
}
|
||||
format
|
||||
type
|
||||
averageScore
|
||||
popularity
|
||||
episodes
|
||||
season
|
||||
hashtag
|
||||
isAdult
|
||||
startDate {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
endDate {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
}
|
||||
}
|
||||
}&variables={ ""query"":""{0}"",""type"":""ANIME""}";
|
||||
public string AniList_anime_link = @"https://graphql.anilist.co/api/v2?query=query($id: Int!, $type: MediaType) {
|
||||
Media(id: $id, type: $type)
|
||||
{
|
||||
id
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
native
|
||||
public string AnimeLink = @"https://graphql.anilist.co/api/v2?query=
|
||||
query($id: Int!, $type: MediaType) {
|
||||
Media(id: $id, type: $type) {
|
||||
id
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
native
|
||||
userPreferred
|
||||
}
|
||||
startDate {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
endDate {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
coverImage {
|
||||
large
|
||||
medium
|
||||
}
|
||||
bannerImage
|
||||
format
|
||||
}
|
||||
startDate {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
endDate {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
coverImage {
|
||||
medium
|
||||
large
|
||||
extraLarge
|
||||
}
|
||||
bannerImage
|
||||
format
|
||||
type
|
||||
status
|
||||
episodes
|
||||
chapters
|
||||
volumes
|
||||
season
|
||||
seasonYear
|
||||
description
|
||||
averageScore
|
||||
meanScore
|
||||
genres
|
||||
synonyms
|
||||
duration
|
||||
tags {
|
||||
id
|
||||
name
|
||||
category
|
||||
}
|
||||
nextAiringEpisode {
|
||||
airingAt
|
||||
timeUntilAiring
|
||||
airingAt
|
||||
timeUntilAiring
|
||||
episode
|
||||
}
|
||||
}
|
||||
}&variables={ ""id"":""{0}"",""type"":""ANIME""}";
|
||||
private const string AniList_anime_char_link = @"https://graphql.anilist.co/api/v2?query=query($id: Int!, $type: MediaType, $page: Int = 1) {
|
||||
Media(id: $id, type: $type) {
|
||||
id
|
||||
characters(page: $page, sort: [ROLE]) {
|
||||
pageInfo {
|
||||
total
|
||||
perPage
|
||||
hasNextPage
|
||||
currentPage
|
||||
lastPage
|
||||
|
||||
studios {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
isAnimationStudio
|
||||
}
|
||||
}
|
||||
characters(sort: [ROLE]) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name {
|
||||
first
|
||||
last
|
||||
full
|
||||
}
|
||||
image {
|
||||
medium
|
||||
@ -129,6 +122,7 @@ query ($query: String, $type: MediaType) {
|
||||
name {
|
||||
first
|
||||
last
|
||||
full
|
||||
native
|
||||
}
|
||||
image {
|
||||
@ -149,233 +143,78 @@ query ($query: String, $type: MediaType) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API call to get the anime with the id
|
||||
/// API call to get the anime with the given id
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<RemoteSearchResult> GetAnime(string id)
|
||||
public async Task<Media> GetAnime(string id)
|
||||
{
|
||||
RootObject WebContent = await WebRequestAPI(AniList_anime_link.Replace("{0}",id));
|
||||
|
||||
var result = new RemoteSearchResult
|
||||
{
|
||||
Name = ""
|
||||
};
|
||||
|
||||
result.SearchProviderName = WebContent.data.Media.title.romaji;
|
||||
result.ImageUrl = WebContent.data.Media.coverImage.large;
|
||||
result.SetProviderId(ProviderNames.AniList, id);
|
||||
result.Overview = WebContent.data.Media.description;
|
||||
|
||||
return result;
|
||||
return (await WebRequestAPI(AnimeLink.Replace("{0}", id))).data?.Media;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API call to select the lang
|
||||
/// </summary>
|
||||
/// <param name="WebContent"></param>
|
||||
/// <param name="preference"></param>
|
||||
/// <param name="language"></param>
|
||||
/// <returns></returns>
|
||||
private string SelectName(RootObject WebContent, TitlePreferenceType preference, string language)
|
||||
{
|
||||
if (preference == TitlePreferenceType.Localized && language == "en")
|
||||
return WebContent.data.Media.title.english;
|
||||
if (preference == TitlePreferenceType.Japanese)
|
||||
return WebContent.data.Media.title.native;
|
||||
|
||||
return WebContent.data.Media.title.romaji;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API call to get the title with the right lang
|
||||
/// </summary>
|
||||
/// <param name="lang"></param>
|
||||
/// <param name="WebContent"></param>
|
||||
/// <returns></returns>
|
||||
public string Get_title(string lang, RootObject WebContent)
|
||||
{
|
||||
switch (lang)
|
||||
{
|
||||
case "en":
|
||||
return WebContent.data.Media.title.english;
|
||||
|
||||
case "jap":
|
||||
return WebContent.data.Media.title.native;
|
||||
|
||||
//Default is jap_r
|
||||
default:
|
||||
return WebContent.data.Media.title.romaji;
|
||||
}
|
||||
}
|
||||
public async Task<List<PersonInfo>> GetPersonInfo(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
List<PersonInfo> lpi = new List<PersonInfo>();
|
||||
RootObject WebContent = await WebRequestAPI(AniList_anime_char_link.Replace("{0}", id.ToString()));
|
||||
foreach (Edge edge in WebContent.data.Media.characters.edges)
|
||||
{
|
||||
PersonInfo pi = new PersonInfo();
|
||||
pi.Name = edge.node.name.first+" "+ edge.node.name.last;
|
||||
pi.ImageUrl = edge.node.image.large;
|
||||
pi.Role = edge.role;
|
||||
}
|
||||
return lpi;
|
||||
}
|
||||
/// <summary>
|
||||
/// Convert int to Guid
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public async static Task<Guid> ToGuid(int value, CancellationToken cancellationToken)
|
||||
{
|
||||
byte[] bytes = new byte[16];
|
||||
await Task.Run(() => BitConverter.GetBytes(value).CopyTo(bytes, 0), cancellationToken);
|
||||
return new Guid(bytes);
|
||||
}
|
||||
/// <summary>
|
||||
/// API call to get the genre of the anime
|
||||
/// </summary>
|
||||
/// <param name="WebContent"></param>
|
||||
/// <returns></returns>
|
||||
public List<string> Get_Genre(RootObject WebContent)
|
||||
{
|
||||
|
||||
return WebContent.data.Media.genres;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API call to get the img url
|
||||
/// </summary>
|
||||
/// <param name="WebContent"></param>
|
||||
/// <returns></returns>
|
||||
public string Get_ImageUrl(RootObject WebContent)
|
||||
{
|
||||
return WebContent.data.Media.coverImage.large;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API call too get the rating
|
||||
/// </summary>
|
||||
/// <param name="WebContent"></param>
|
||||
/// <returns></returns>
|
||||
public string Get_Rating(RootObject WebContent)
|
||||
{
|
||||
return (WebContent.data.Media.averageScore / 10).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API call to get the description
|
||||
/// </summary>
|
||||
/// <param name="WebContent"></param>
|
||||
/// <returns></returns>
|
||||
public string Get_Overview(RootObject WebContent)
|
||||
{
|
||||
return WebContent.data.Media.description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API call to search a title and return the right one back
|
||||
/// API call to search a title and return the first result
|
||||
/// </summary>
|
||||
/// <param name="title"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<string> Search_GetSeries(string title, CancellationToken cancellationToken)
|
||||
public async Task<MediaSearchResult> Search_GetSeries(string title, CancellationToken cancellationToken)
|
||||
{
|
||||
string result = null;
|
||||
// Reimplemented instead of calling Search_GetSeries_list() for efficiency
|
||||
RootObject WebContent = await WebRequestAPI(SearchLink.Replace("{0}", title));
|
||||
foreach (Medium media in WebContent.data.Page.media) {
|
||||
//get id
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
if (await Equals_check.Compare_strings(media.title.romaji, title, cancellationToken))
|
||||
{
|
||||
return media.id.ToString();
|
||||
}
|
||||
if (await Equals_check.Compare_strings(media.title.english, title, cancellationToken))
|
||||
{
|
||||
return media.id.ToString();
|
||||
}
|
||||
//Disabled due to false result.
|
||||
/*if (await Task.Run(() => Equals_check.Compare_strings(media.title.native, title)))
|
||||
{
|
||||
return media.id.ToString();
|
||||
}*/
|
||||
}
|
||||
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API call to search a title and return a list back
|
||||
/// </summary>
|
||||
/// <param name="title"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<List<string>> Search_GetSeries_list(string title, CancellationToken cancellationToken)
|
||||
{
|
||||
List<string> result = new List<string>();
|
||||
RootObject WebContent = await WebRequestAPI(SearchLink.Replace("{0}", title));
|
||||
foreach (Medium media in WebContent.data.Page.media)
|
||||
foreach (MediaSearchResult media in WebContent.data.Page.media)
|
||||
{
|
||||
//get id
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
if (await Equals_check.Compare_strings(media.title.romaji, title, cancellationToken))
|
||||
{
|
||||
result.Add(media.id.ToString());
|
||||
}
|
||||
if (await Equals_check.Compare_strings(media.title.english, title, cancellationToken))
|
||||
{
|
||||
result.Add(media.id.ToString());
|
||||
}
|
||||
//Disabled due to false result.
|
||||
/*if (await Task.Run(() => Equals_check.Compare_strings(media.title.native, title)))
|
||||
{
|
||||
result.Add(media.id.ToString());
|
||||
}*/
|
||||
}
|
||||
|
||||
catch (Exception) { }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SEARCH Title
|
||||
/// </summary>
|
||||
public async Task<string> FindSeries(string title, CancellationToken cancellationToken)
|
||||
{
|
||||
string aid = await Search_GetSeries(title, cancellationToken);
|
||||
if (!string.IsNullOrEmpty(aid))
|
||||
{
|
||||
return aid;
|
||||
}
|
||||
aid = await Search_GetSeries(await Equals_check.Clear_name(title, cancellationToken), cancellationToken);
|
||||
if (!string.IsNullOrEmpty(aid))
|
||||
{
|
||||
return aid;
|
||||
return media;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GET website content from the link
|
||||
/// API call to search a title and return a list of results
|
||||
/// </summary>
|
||||
/// <param name="title"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<List<MediaSearchResult>> Search_GetSeries_list(string title, CancellationToken cancellationToken)
|
||||
{
|
||||
return (await WebRequestAPI(SearchLink.Replace("{0}", title))).data.Page.media;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search for anime with the given title. Attempts to fuzzy search by removing special characters
|
||||
/// </summary>
|
||||
/// <param name="title"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<string> FindSeries(string title, CancellationToken cancellationToken)
|
||||
{
|
||||
MediaSearchResult result = await Search_GetSeries(title, cancellationToken);
|
||||
if (result != null)
|
||||
{
|
||||
return result.id.ToString();
|
||||
}
|
||||
|
||||
result = await Search_GetSeries(await Equals_check.Clear_name(title, cancellationToken), cancellationToken);
|
||||
if (result != null)
|
||||
{
|
||||
return result.id.ToString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GET and parse JSON content from link, deserialize into a RootObject
|
||||
/// </summary>
|
||||
/// <param name="link"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<RootObject> WebRequestAPI(string link)
|
||||
{
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,25 @@
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace Jellyfin.Plugin.Anime.Providers.AniList
|
||||
{
|
||||
public class AniListExternalId : IExternalId
|
||||
{
|
||||
public bool Supports(IHasProviderIds item)
|
||||
=> item is Series;
|
||||
=> item is Series || item is Movie;
|
||||
|
||||
public string Name
|
||||
public string ProviderName
|
||||
=> "AniList";
|
||||
|
||||
public string Key
|
||||
=> ProviderNames.AniList;
|
||||
|
||||
public ExternalIdMediaType? Type
|
||||
=> ExternalIdMediaType.Series;
|
||||
|
||||
public string UrlFormatString
|
||||
=> "https://anilist.co/anime/{0}/";
|
||||
}
|
||||
|
@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace Jellyfin.Plugin.Anime.Providers.AniList
|
||||
{
|
||||
public class AniListImageProvider : IRemoteImageProvider
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly AniListApi _aniListApi;
|
||||
public AniListImageProvider(IHttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_aniListApi = new AniListApi();
|
||||
}
|
||||
|
||||
public string Name => "AniList";
|
||||
|
||||
public bool Supports(BaseItem item) => item is Series || item is Season || item is Movie;
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||
{
|
||||
return new[] { ImageType.Primary, ImageType.Banner };
|
||||
}
|
||||
|
||||
public Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var seriesId = item.GetProviderId(ProviderNames.AniList);
|
||||
return GetImages(seriesId, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RemoteImageInfo>> GetImages(string aid, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = new List<RemoteImageInfo>();
|
||||
|
||||
if (!string.IsNullOrEmpty(aid))
|
||||
{
|
||||
Media media = await _aniListApi.GetAnime(aid);
|
||||
if (media != null)
|
||||
{
|
||||
if (media.GetImageUrl() != null)
|
||||
{
|
||||
list.Add(new RemoteImageInfo
|
||||
{
|
||||
ProviderName = Name,
|
||||
Type = ImageType.Primary,
|
||||
Url = media.GetImageUrl()
|
||||
});
|
||||
}
|
||||
|
||||
if (media.bannerImage != null)
|
||||
{
|
||||
list.Add(new RemoteImageInfo
|
||||
{
|
||||
ProviderName = Name,
|
||||
Type = ImageType.Banner,
|
||||
Url = media.bannerImage
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
UserAgent = Constants.UserAgent,
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
113
Jellyfin.Plugin.Anime/Providers/AniList/AniListMovieProvider.cs
Normal file
113
Jellyfin.Plugin.Anime/Providers/AniList/AniListMovieProvider.cs
Normal file
@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
//API v2
|
||||
namespace Jellyfin.Plugin.Anime.Providers.AniList
|
||||
{
|
||||
public class AniListMovieProvider : IRemoteMetadataProvider<Movie, MovieInfo>, IHasOrder
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IApplicationPaths _paths;
|
||||
private readonly ILogger _log;
|
||||
private readonly AniListApi _aniListApi;
|
||||
public int Order => -2;
|
||||
public string Name => "AniList";
|
||||
|
||||
public AniListMovieProvider(IApplicationPaths appPaths, IHttpClient httpClient, ILogger<AniListMovieProvider> logger)
|
||||
{
|
||||
_log = logger;
|
||||
_httpClient = httpClient;
|
||||
_aniListApi = new AniListApi();
|
||||
_paths = appPaths;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new MetadataResult<Movie>();
|
||||
Media media = null;
|
||||
|
||||
var aid = info.ProviderIds.GetOrDefault(ProviderNames.AniList);
|
||||
if (!string.IsNullOrEmpty(aid))
|
||||
{
|
||||
media = await _aniListApi.GetAnime(aid);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.LogInformation("Start AniList... Searching({Name})", info.Name);
|
||||
MediaSearchResult msr = await _aniListApi.Search_GetSeries(info.Name, cancellationToken);
|
||||
if (msr != null)
|
||||
{
|
||||
media = await _aniListApi.GetAnime(msr.id.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
if (media != null)
|
||||
{
|
||||
result.HasMetadata = true;
|
||||
result.Item = media.ToMovie();
|
||||
result.People = media.GetPeopleInfo();
|
||||
result.Provider = ProviderNames.AniList;
|
||||
StoreImageUrl(media.id.ToString(), media.GetImageUrl(), "image");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var results = new List<RemoteSearchResult>();
|
||||
|
||||
var aid = searchInfo.ProviderIds.GetOrDefault(ProviderNames.AniList);
|
||||
if (!string.IsNullOrEmpty(aid))
|
||||
{
|
||||
Media aid_result = await _aniListApi.GetAnime(aid).ConfigureAwait(false);
|
||||
if (aid_result != null)
|
||||
{
|
||||
results.Add(aid_result.ToSearchResult());
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(searchInfo.Name))
|
||||
{
|
||||
List<MediaSearchResult> name_results = await _aniListApi.Search_GetSeries_list(searchInfo.Name, cancellationToken).ConfigureAwait(false);
|
||||
foreach (var media in name_results)
|
||||
{
|
||||
results.Add(media.ToSearchResult());
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private void StoreImageUrl(string series, string url, string type)
|
||||
{
|
||||
var path = Path.Combine(_paths.CachePath, "anilist", type, series + ".txt");
|
||||
var directory = Path.GetDirectoryName(path);
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
File.WriteAllText(path, url);
|
||||
}
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
UserAgent = Constants.UserAgent,
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ namespace Jellyfin.Plugin.Anime.Providers.AniList
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IApplicationPaths _paths;
|
||||
private readonly ILogger _log;
|
||||
private readonly ILogger<AniListSeriesProvider> _log;
|
||||
private readonly AniListApi _aniListApi;
|
||||
public int Order => -2;
|
||||
public string Name => "AniList";
|
||||
@ -36,33 +36,30 @@ namespace Jellyfin.Plugin.Anime.Providers.AniList
|
||||
public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new MetadataResult<Series>();
|
||||
Media media = null;
|
||||
|
||||
var aid = info.ProviderIds.GetOrDefault(ProviderNames.AniList);
|
||||
if (string.IsNullOrEmpty(aid))
|
||||
{
|
||||
_log.LogInformation("Start AniList... Searching({Name})", info.Name);
|
||||
aid = await _aniListApi.FindSeries(info.Name, cancellationToken);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(aid))
|
||||
{
|
||||
RootObject WebContent = await _aniListApi.WebRequestAPI(_aniListApi.AniList_anime_link.Replace("{0}", aid));
|
||||
result.Item = new Series();
|
||||
result.HasMetadata = true;
|
||||
|
||||
result.People = await _aniListApi.GetPersonInfo(WebContent.data.Media.id, cancellationToken);
|
||||
result.Item.ProviderIds.Add(ProviderNames.AniList, aid);
|
||||
result.Item.Overview = WebContent.data.Media.description;
|
||||
try
|
||||
media = await _aniListApi.GetAnime(aid);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.LogInformation("Start AniList... Searching({Name})", info.Name);
|
||||
MediaSearchResult msr = await _aniListApi.Search_GetSeries(info.Name, cancellationToken);
|
||||
if (msr != null)
|
||||
{
|
||||
//AniList has a max rating of 5
|
||||
result.Item.CommunityRating = WebContent.data.Media.averageScore / 10;
|
||||
media = await _aniListApi.GetAnime(msr.id.ToString());
|
||||
}
|
||||
catch (Exception) { }
|
||||
foreach (var genre in _aniListApi.Get_Genre(WebContent))
|
||||
result.Item.AddGenre(genre);
|
||||
GenreHelper.CleanupGenres(result.Item);
|
||||
StoreImageUrl(aid, WebContent.data.Media.coverImage.large, "image");
|
||||
}
|
||||
|
||||
if (media != null)
|
||||
{
|
||||
result.HasMetadata = true;
|
||||
result.Item = media.ToSeries();
|
||||
result.People = media.GetPeopleInfo();
|
||||
result.Provider = ProviderNames.AniList;
|
||||
StoreImageUrl(media.id.ToString(), media.GetImageUrl(), "image");
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -70,27 +67,28 @@ namespace Jellyfin.Plugin.Anime.Providers.AniList
|
||||
|
||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var results = new Dictionary<string, RemoteSearchResult>();
|
||||
var results = new List<RemoteSearchResult>();
|
||||
|
||||
var aid = searchInfo.ProviderIds.GetOrDefault(ProviderNames.AniList);
|
||||
if (!string.IsNullOrEmpty(aid))
|
||||
{
|
||||
if (!results.ContainsKey(aid))
|
||||
Media aid_result = await _aniListApi.GetAnime(aid).ConfigureAwait(false);
|
||||
if (aid_result != null)
|
||||
{
|
||||
results.Add(aid, await _aniListApi.GetAnime(aid).ConfigureAwait(false));
|
||||
results.Add(aid_result.ToSearchResult());
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(searchInfo.Name))
|
||||
{
|
||||
List<string> ids = await _aniListApi.Search_GetSeries_list(searchInfo.Name, cancellationToken).ConfigureAwait(false);
|
||||
foreach (string a in ids)
|
||||
List<MediaSearchResult> name_results = await _aniListApi.Search_GetSeries_list(searchInfo.Name, cancellationToken).ConfigureAwait(false);
|
||||
foreach (var media in name_results)
|
||||
{
|
||||
results.Add(a, await _aniListApi.GetAnime(a).ConfigureAwait(false));
|
||||
results.Add(media.ToSearchResult());
|
||||
}
|
||||
}
|
||||
|
||||
return results.Values;
|
||||
return results;
|
||||
}
|
||||
|
||||
private void StoreImageUrl(string series, string url, string type)
|
||||
@ -112,57 +110,4 @@ namespace Jellyfin.Plugin.Anime.Providers.AniList
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class AniListSeriesImageProvider : IRemoteImageProvider
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly AniListApi _aniListApi;
|
||||
public AniListSeriesImageProvider(IHttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_aniListApi = new AniListApi();
|
||||
}
|
||||
|
||||
public string Name => "AniList";
|
||||
|
||||
public bool Supports(BaseItem item) => item is Series || item is Season;
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||
{
|
||||
return new[] { ImageType.Primary };
|
||||
}
|
||||
|
||||
public Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var seriesId = item.GetProviderId(ProviderNames.AniList);
|
||||
return GetImages(seriesId, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RemoteImageInfo>> GetImages(string aid, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = new List<RemoteImageInfo>();
|
||||
|
||||
if (!string.IsNullOrEmpty(aid))
|
||||
{
|
||||
var primary = _aniListApi.Get_ImageUrl(await _aniListApi.WebRequestAPI(_aniListApi.AniList_anime_link.Replace("{0}", aid)));
|
||||
list.Add(new RemoteImageInfo
|
||||
{
|
||||
ProviderName = Name,
|
||||
Type = ImageType.Primary,
|
||||
Url = primary
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
UserAgent = Constants.UserAgent,
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using Jellyfin.Plugin.Anime.Configuration;
|
||||
|
||||
namespace Jellyfin.Plugin.Anime.Providers.AniList
|
||||
{
|
||||
@ -17,51 +23,19 @@ namespace Jellyfin.Plugin.Anime.Providers.AniList
|
||||
{
|
||||
public string medium { get; set; }
|
||||
public string large { get; set; }
|
||||
public string extraLarge { get; set; }
|
||||
}
|
||||
|
||||
public class StartDate
|
||||
public class ApiDate
|
||||
{
|
||||
public int year { get; set; }
|
||||
public int month { get; set; }
|
||||
public int day { get; set; }
|
||||
}
|
||||
|
||||
public class EndDate
|
||||
{
|
||||
public int year { get; set; }
|
||||
public int month { get; set; }
|
||||
public int day { get; set; }
|
||||
}
|
||||
|
||||
public class Medium
|
||||
{
|
||||
public int id { get; set; }
|
||||
public Title title { get; set; }
|
||||
public CoverImage coverImage { get; set; }
|
||||
public string format { get; set; }
|
||||
public string type { get; set; }
|
||||
public int averageScore { get; set; }
|
||||
public int popularity { get; set; }
|
||||
public int episodes { get; set; }
|
||||
public string season { get; set; }
|
||||
public string hashtag { get; set; }
|
||||
public bool isAdult { get; set; }
|
||||
public StartDate startDate { get; set; }
|
||||
public EndDate endDate { get; set; }
|
||||
public object bannerImage { get; set; }
|
||||
public string status { get; set; }
|
||||
public object chapters { get; set; }
|
||||
public object volumes { get; set; }
|
||||
public string description { get; set; }
|
||||
public int meanScore { get; set; }
|
||||
public List<string> genres { get; set; }
|
||||
public List<object> synonyms { get; set; }
|
||||
public object nextAiringEpisode { get; set; }
|
||||
public int? year { get; set; }
|
||||
public int? month { get; set; }
|
||||
public int? day { get; set; }
|
||||
}
|
||||
|
||||
public class Page
|
||||
{
|
||||
public List<Medium> media { get; set; }
|
||||
public List<MediaSearchResult> media { get; set; }
|
||||
}
|
||||
|
||||
public class Data
|
||||
@ -70,31 +44,234 @@ namespace Jellyfin.Plugin.Anime.Providers.AniList
|
||||
public Media Media { get; set; }
|
||||
}
|
||||
|
||||
public class Media
|
||||
/// <summary>
|
||||
/// A slimmed down version of Media to avoid confusion and reduce
|
||||
/// the size of responses when searching.
|
||||
/// </summary>
|
||||
public class MediaSearchResult
|
||||
{
|
||||
public Characters characters { get; set; }
|
||||
public int popularity { get; set; }
|
||||
public object hashtag { get; set; }
|
||||
public bool isAdult { get; set; }
|
||||
public int id { get; set; }
|
||||
public Title title { get; set; }
|
||||
public StartDate startDate { get; set; }
|
||||
public EndDate endDate { get; set; }
|
||||
public ApiDate startDate { get; set; }
|
||||
public CoverImage coverImage { get; set; }
|
||||
public object bannerImage { get; set; }
|
||||
public string format { get; set; }
|
||||
public string type { get; set; }
|
||||
public string status { get; set; }
|
||||
public int episodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the title in configured language
|
||||
/// </summary>
|
||||
/// <param name="language"></param>
|
||||
/// <returns></returns>
|
||||
public string GetPreferredTitle(string language)
|
||||
{
|
||||
PluginConfiguration config = Plugin.Instance.Configuration;
|
||||
if (config.TitlePreference == TitlePreferenceType.Localized)
|
||||
{
|
||||
if (language == "en")
|
||||
{
|
||||
return this.title.english;
|
||||
}
|
||||
if (language == "jap")
|
||||
{
|
||||
return this.title.native;
|
||||
}
|
||||
}
|
||||
if (config.TitlePreference == TitlePreferenceType.Japanese)
|
||||
{
|
||||
return this.title.native;
|
||||
}
|
||||
|
||||
return this.title.romaji;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the highest quality image url
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetImageUrl()
|
||||
{
|
||||
return this.coverImage.extraLarge ?? this.coverImage.large ?? this.coverImage.medium;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the start date as a DateTime object or null if not available
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public DateTime? GetStartDate()
|
||||
{
|
||||
if (this.startDate.year == null || this.startDate.month == null || this.startDate.day == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new DateTime(this.startDate.year.Value, this.startDate.month.Value, this.startDate.day.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a Media/MediaSearchResult object to a RemoteSearchResult
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public RemoteSearchResult ToSearchResult()
|
||||
{
|
||||
return new RemoteSearchResult
|
||||
{
|
||||
Name = this.GetPreferredTitle("en"),
|
||||
ProductionYear = this.startDate.year,
|
||||
PremiereDate = this.GetStartDate(),
|
||||
ImageUrl = this.GetImageUrl(),
|
||||
SearchProviderName = ProviderNames.AniList,
|
||||
ProviderIds = new Dictionary<string, string>() {{ProviderNames.AniList, this.id.ToString()}}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class Media: MediaSearchResult
|
||||
{
|
||||
public int? averageScore { get; set; }
|
||||
public string bannerImage { get; set; }
|
||||
public object chapters { get; set; }
|
||||
public object volumes { get; set; }
|
||||
public string season { get; set; }
|
||||
public Characters characters { get; set; }
|
||||
public string description { get; set; }
|
||||
public int averageScore { get; set; }
|
||||
public int meanScore { get; set; }
|
||||
public int? duration { get; set; }
|
||||
public ApiDate endDate { get; set; }
|
||||
public int? episodes { get; set; }
|
||||
public string format { get; set; }
|
||||
public List<string> genres { get; set; }
|
||||
public List<object> synonyms { get; set; }
|
||||
public object hashtag { get; set; }
|
||||
public bool isAdult { get; set; }
|
||||
public int? meanScore { get; set; }
|
||||
public object nextAiringEpisode { get; set; }
|
||||
public int? popularity { get; set; }
|
||||
public string season { get; set; }
|
||||
public int? seasonYear { get; set; }
|
||||
public string status { get; set; }
|
||||
public StudioConnection studios { get; set; }
|
||||
public List<object> synonyms { get; set; }
|
||||
public List<Tag> tags { get; set; }
|
||||
public string type { get; set; }
|
||||
public object volumes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the rating, normalized to 1-10
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public float GetRating()
|
||||
{
|
||||
return (this.averageScore ?? 0) / 10f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the end date as a DateTime object or null if not available
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public DateTime? GetEndDate()
|
||||
{
|
||||
if (this.endDate.year == null || this.endDate.month == null || this.endDate.day == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new DateTime(this.endDate.year.Value, this.endDate.month.Value, this.endDate.day.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of studio names
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<string> GetStudioNames()
|
||||
{
|
||||
List<string> results = new List<string>();
|
||||
foreach (Studio node in this.studios.nodes)
|
||||
{
|
||||
results.Add(node.name);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of PersonInfo for voice actors
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<PersonInfo> GetPeopleInfo()
|
||||
{
|
||||
List<PersonInfo> lpi = new List<PersonInfo>();
|
||||
foreach (CharacterEdge edge in this.characters.edges)
|
||||
{
|
||||
foreach (VoiceActor va in edge.voiceActors)
|
||||
{
|
||||
PeopleHelper.AddPerson(lpi, new PersonInfo {
|
||||
Name = va.name.full,
|
||||
ImageUrl = va.image.large ?? va.image.medium,
|
||||
Role = edge.node.name.full,
|
||||
Type = PersonType.Actor,
|
||||
ProviderIds = new Dictionary<string, string>() {{ProviderNames.AniList, this.id.ToString()}}
|
||||
});
|
||||
}
|
||||
}
|
||||
return lpi;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of tag names
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<string> GetTagNames()
|
||||
{
|
||||
List<string> results = new List<string>();
|
||||
foreach (Tag tag in this.tags)
|
||||
{
|
||||
results.Add(tag.name);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a Media object to a Series
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Series ToSeries()
|
||||
{
|
||||
var result = new Series {
|
||||
Name = this.GetPreferredTitle("en"),
|
||||
Overview = this.description,
|
||||
ProductionYear = this.startDate.year,
|
||||
PremiereDate = this.GetStartDate(),
|
||||
EndDate = this.GetStartDate(),
|
||||
CommunityRating = this.GetRating(),
|
||||
RunTimeTicks = this.duration.HasValue ? TimeSpan.FromMinutes(this.duration.Value).Ticks : (long?)null,
|
||||
Genres = this.genres.ToArray(),
|
||||
Tags = this.GetTagNames().ToArray(),
|
||||
Studios = this.GetStudioNames().ToArray(),
|
||||
ProviderIds = new Dictionary<string, string>() {{ProviderNames.AniList, this.id.ToString()}}
|
||||
};
|
||||
|
||||
if (this.status == "FINISHED" || this.status == "CANCELLED")
|
||||
{
|
||||
result.Status = SeriesStatus.Ended;
|
||||
}
|
||||
else if (this.status == "RELEASING")
|
||||
{
|
||||
result.Status = SeriesStatus.Continuing;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a Media object to a Movie
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Movie ToMovie()
|
||||
{
|
||||
return new Movie {
|
||||
Name = this.GetPreferredTitle("en"),
|
||||
Overview = this.description,
|
||||
ProductionYear = this.startDate.year,
|
||||
PremiereDate = this.GetStartDate(),
|
||||
EndDate = this.GetStartDate(),
|
||||
CommunityRating = this.GetRating(),
|
||||
Genres = this.genres.ToArray(),
|
||||
Tags = this.GetTagNames().ToArray(),
|
||||
Studios = this.GetStudioNames().ToArray(),
|
||||
ProviderIds = new Dictionary<string, string>() {{ProviderNames.AniList, this.id.ToString()}}
|
||||
};
|
||||
}
|
||||
}
|
||||
public class PageInfo
|
||||
{
|
||||
@ -117,10 +294,10 @@ namespace Jellyfin.Plugin.Anime.Providers.AniList
|
||||
public string large { get; set; }
|
||||
}
|
||||
|
||||
public class Node
|
||||
public class Character
|
||||
{
|
||||
public int id { get; set; }
|
||||
public Name name { get; set; }
|
||||
public Name2 name { get; set; }
|
||||
public Image image { get; set; }
|
||||
}
|
||||
|
||||
@ -128,34 +305,48 @@ namespace Jellyfin.Plugin.Anime.Providers.AniList
|
||||
{
|
||||
public string first { get; set; }
|
||||
public string last { get; set; }
|
||||
public string full { get; set; }
|
||||
public string native { get; set; }
|
||||
}
|
||||
|
||||
public class Image2
|
||||
{
|
||||
public string medium { get; set; }
|
||||
public string large { get; set; }
|
||||
}
|
||||
|
||||
public class VoiceActor
|
||||
{
|
||||
public int id { get; set; }
|
||||
public Name2 name { get; set; }
|
||||
public Image2 image { get; set; }
|
||||
public Image image { get; set; }
|
||||
public string language { get; set; }
|
||||
}
|
||||
|
||||
public class Edge
|
||||
public class CharacterEdge
|
||||
{
|
||||
public Node node { get; set; }
|
||||
public Character node { get; set; }
|
||||
public string role { get; set; }
|
||||
public List<VoiceActor> voiceActors { get; set; }
|
||||
}
|
||||
|
||||
public class Characters
|
||||
{
|
||||
public PageInfo pageInfo { get; set; }
|
||||
public List<Edge> edges { get; set; }
|
||||
public List<CharacterEdge> edges { get; set; }
|
||||
}
|
||||
|
||||
public class Tag
|
||||
{
|
||||
public int id { get; set; }
|
||||
public string name { get; set; }
|
||||
public string description { get; set; }
|
||||
public string category { get; set; }
|
||||
}
|
||||
|
||||
public class Studio
|
||||
{
|
||||
public int id { get; set; }
|
||||
public string name { get; set; }
|
||||
public bool isAnimationStudio { get; set; }
|
||||
}
|
||||
|
||||
public class StudioConnection
|
||||
{
|
||||
public List<Studio> nodes { get; set; }
|
||||
}
|
||||
|
||||
public class RootObject
|
||||
|
@ -1,6 +1,7 @@
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace Jellyfin.Plugin.Anime.Providers.AniSearch
|
||||
{
|
||||
@ -9,12 +10,15 @@ namespace Jellyfin.Plugin.Anime.Providers.AniSearch
|
||||
public bool Supports(IHasProviderIds item)
|
||||
=> item is Series;
|
||||
|
||||
public string Name
|
||||
public string ProviderName
|
||||
=> "AniSearch";
|
||||
|
||||
public string Key
|
||||
=> ProviderNames.AniSearch;
|
||||
|
||||
public ExternalIdMediaType? Type
|
||||
=> ExternalIdMediaType.Series;
|
||||
|
||||
public string UrlFormatString
|
||||
=> "https://www.anisearch.com/anime/{0}";
|
||||
}
|
||||
|
@ -18,13 +18,13 @@ namespace Jellyfin.Plugin.Anime.Providers.AniSearch
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IApplicationPaths _paths;
|
||||
private readonly ILogger _log;
|
||||
private readonly ILogger<AniSearchSeriesProvider> _log;
|
||||
public int Order => -3;
|
||||
public string Name => "AniSearch";
|
||||
|
||||
public AniSearchSeriesProvider(IApplicationPaths appPaths, IHttpClient httpClient, ILoggerFactory loggerFactory)
|
||||
public AniSearchSeriesProvider(IApplicationPaths appPaths, IHttpClient httpClient, ILogger<AniSearchSeriesProvider> logger)
|
||||
{
|
||||
_log = loggerFactory.CreateLogger("AniSearch");
|
||||
_log = logger;
|
||||
_httpClient = httpClient;
|
||||
_paths = appPaths;
|
||||
}
|
||||
|
@ -13,9 +13,9 @@ namespace Jellyfin.Plugin.Anime.Providers
|
||||
{
|
||||
internal class Equals_check
|
||||
{
|
||||
public readonly ILogger _logger;
|
||||
public readonly ILogger<Equals_check> _logger;
|
||||
|
||||
public Equals_check(ILogger logger)
|
||||
public Equals_check(ILogger<Equals_check> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
---
|
||||
name: "Anime"
|
||||
guid: "a4df60c5-6ab4-412a-8f79-2cab93fb2bc5"
|
||||
version: "8.0.0.0"
|
||||
targetAbi: "10.5.0.0"
|
||||
version: "9.0.0.0"
|
||||
targetAbi: "10.6.0.0"
|
||||
owner: "jellyfin"
|
||||
overview: "Manage your anime from Jellyfin"
|
||||
description: >
|
||||
|
Loading…
Reference in New Issue
Block a user