summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Api/JCoverSharedController.cs132
-rw-r--r--Api/coverscript.js37
-rw-r--r--Folder.DotSettings.user19
-rw-r--r--ImageProvider.cs4
-rw-r--r--POJO.cs42
-rw-r--r--SeriesImageProvider.cs25
6 files changed, 247 insertions, 12 deletions
diff --git a/Api/JCoverSharedController.cs b/Api/JCoverSharedController.cs
index a5eecc1..47a8c8a 100644
--- a/Api/JCoverSharedController.cs
+++ b/Api/JCoverSharedController.cs
@@ -1,9 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web;
+using Jellyfin.Api;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.JCoverXtremePro.Api;
[ApiController]
[Route("JCoverXtreme")]
-public class JCoverSharedController : ControllerBase
+// [Authorize(Policy = "RequiresElevation")]
+public class JCoverSharedController : BaseJellyfinApiController
{
+ private readonly IProviderManager _providerManager;
+ private readonly ILibraryManager _libraryManager;
+
+ /// <summary>
+ /// This key is appended to image URLs to inform the frontend about the presence of a potential mass-download.
+ /// Keep in sync with coverscript.js#URL_META_KEY.
+ /// </summary>
+ public static string URL_META_KEY = "JCoverXtremeProMeta";
+
+ public JCoverSharedController(
+ IProviderManager providerManager,
+ IServerApplicationPaths applicationPaths,
+ ILibraryManager libraryManager)
+ {
+ _providerManager = providerManager;
+ _libraryManager = libraryManager;
+ }
+
+ public static string AppendUrlMeta(string baseUrl, string key, string value)
+ {
+ return baseUrl + (baseUrl.Contains('?', StringComparison.InvariantCulture) ? "&" : "?") +
+ HttpUtility.UrlEncode(key) + "=" + HttpUtility.UrlEncode(value);
+ }
+
+ public class SetMeta
+ {
+ public Guid seriesId { get; set; }
+ public string setId { get; set; }
+ }
+
+ public static string PackSetInfo(string baseUrl, Series series, POJO.Set set)
+ {
+ return AppendUrlMeta(
+ baseUrl,
+ URL_META_KEY,
+ JsonSerializer.Serialize(new SetMeta
+ {
+ setId = set.id,
+ seriesId = series.Id
+ }));
+ }
+
+ [HttpPost("DownloadSeries")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ public async Task<ActionResult> DownloadEntireSeriesImages(
+ [FromBody, Required] JsonObject setMeta
+ )
+ {
+ // TODO: handle missing fields, local seasons missing, series missing, etc.
+ var setMetaObj = JsonSerializer.Deserialize<SetMeta>(setMeta);
+ var series = _libraryManager.GetItemById(setMetaObj.seriesId) as Series;
+ var jsonMeta = await MediuxDownloader.instance.GetMediuxMetadata($"https://mediux.pro/sets/{setMetaObj.setId}")
+ .ConfigureAwait(false);
+ var set = JsonSerializer.Deserialize<POJO.SetData>(jsonMeta).set;
+ Dictionary<string, (int, int)> lut = new();
+ foreach (var showSeason in set.show.seasons)
+ {
+ foreach (var showEpisode in showSeason.episodes)
+ {
+ lut[showEpisode.id] = (showSeason.season_number, showEpisode.episode_number);
+ }
+ }
+
+ Dictionary<(int, int), POJO.File> files = new();
+ foreach (var file in set.files)
+ {
+ var episode = file.episode_id;
+ if (episode == null)
+ {
+ continue;
+ }
+
+ var tup = lut[episode.id];
+ files[tup] = file;
+ }
+
+ foreach (var item in series.GetSeasons(null, new DtoOptions(true)))
+ {
+ var season = item as Season;
+ var seasonNumber = season.GetLookupInfo().IndexNumber.Value;
+ Plugin.Logger.LogInformation($"Season id: {seasonNumber}:");
+ foreach (var itemAgain in season.GetEpisodes())
+ {
+ var episode = itemAgain as Episode;
+ var episodeNumber = episode.GetLookupInfo().IndexNumber.Value;
+ Plugin.Logger.LogInformation($" * Episode id: {episodeNumber} {episode.Name}");
+ POJO.File file;
+ if (files.TryGetValue((seasonNumber, episodeNumber), out file))
+ {
+ Plugin.Logger.LogInformation($" Found cover: {file.downloadUrl}");
+ // await _providerManager.SaveImage(item, imageUrl, type, null, CancellationToken.None)
+ // .ConfigureAwait(false);
+
+ // await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
+ await _providerManager.SaveImage(
+ episode, file.downloadUrl,
+ // Note: this needs to be updated if SeriesImageProvider ever supports more image types
+ ImageType.Primary,
+ null,
+ CancellationToken.None
+ ).ConfigureAwait(false);
+
+ await episode.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None)
+ .ConfigureAwait(false);
+ }
+ }
+ }
+
+ return Empty;
+ }
} \ No newline at end of file
diff --git a/Api/coverscript.js b/Api/coverscript.js
index 24f3c34..9f3eb7f 100644
--- a/Api/coverscript.js
+++ b/Api/coverscript.js
@@ -19,9 +19,12 @@
/**
*
* @param {HTMLElement} cloneFrom
+ * @param {string} setMeta
* @return {HTMLElement}
*/
- function createDownloadSeriesButton(cloneFrom) {
+ function createDownloadSeriesButton(
+ cloneFrom,
+ setMeta) {
/*<button is="paper-icon-button-light" class="btnDownloadRemoteImage autoSize paper-icon-button-light" raised"="" title="Download"><span class="material-icons cloud_download" aria-hidden="true"></span></button>*/
//import LayersIcon from '@mui/icons-material/Layers';
//import CloudDownloadIcon from '@mui/icons-material/CloudDownload';
@@ -35,12 +38,35 @@
element.appendChild(icon)
element.addEventListener("click", ev => {
ev.preventDefault()
-
- alert("YOU HAVE JUST BEEN INTERDICTED BY THE JCOVERXTREMEPRO SERIES DOWNLOADIFICATOR")
+ console.log("Executing mass covering event! We will try to download the entirety of set " + setMeta)
+ fetch("/JCoverXtreme/DownloadSeries",
+ {
+ method: 'POST',
+ body: setMeta,
+ headers: {
+ "content-type": "application/json"
+ }
+ }).then(console.log) // TODO: check out the root somehow. for now just assume /
})
return element
}
+ /**
+ * Keep in sync with JCoverSharedController.URL_META_KEY
+ * @type {string}
+ */
+ const URL_META_KEY = "JCoverXtremeProMeta"
+
+ /**
+ * Extract the JCoverXtremePro metadata from an image url.
+ *
+ * @param {string} url
+ * @return {string}
+ */
+ function extractSetMeta(url) {
+ return new URL(url).searchParams.get(URL_META_KEY)
+ }
+
const observer = new MutationObserver(() => {
console.log("JCoverXtremePro observation was triggered!")
console.log("Listing all download buttons")
@@ -51,9 +77,12 @@
buttons.forEach(element => {
const downloadRowContainer = findParent(element, ".cardText")
+ const cardContainer = findParent(element, ".cardBox")
+ const cardImage = cardContainer.querySelector("a.cardImageContainer[href]")
+ const setMeta = extractSetMeta(cardImage.href)
if (downloadRowContainer.querySelector(`.${injectionMarker}`)) return
// TODO: extract information about the series, and check if this is at all viable
- downloadRowContainer.appendChild(createDownloadSeriesButton(element))
+ downloadRowContainer.appendChild(createDownloadSeriesButton(element, setMeta))
})
})
diff --git a/Folder.DotSettings.user b/Folder.DotSettings.user
index 87f4e76..8cd3da1 100644
--- a/Folder.DotSettings.user
+++ b/Folder.DotSettings.user
@@ -1,5 +1,22 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AActionResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcf607849fce840fcb02ecfccf47e7bce1dc400_003Fe0_003F073c6be1_003FActionResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthorizeAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F7c2ecf13f5ec4499a77a06d8880bd5c415800_003F77_003F5ce6003f_003FAuthorizeAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABaseItem_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ff5158e2ef5269abc9726c67e850a8988a202b4911c07d9781389d71e7e227_003FBaseItem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AControllerBase_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcf607849fce840fcb02ecfccf47e7bce1dc400_003F86_003F5f98b882_003FControllerBase_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADtoOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fb4cecdec97dbab1be24731f1386a7fefabf47bec533415635c7ca430dc27_003FDtoOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEpisode_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F1badb499b414e42f148749e92584cbf5d72618b72d771344ad7f7a053edf693_003FEpisode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
- <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMovie_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F1e53cde0ae407da5e0d8b3675976ee156dbd2d3335bf707cb079be17fa471_003FMovie_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary> \ No newline at end of file
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFromQueryAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcf607849fce840fcb02ecfccf47e7bce1dc400_003Fb4_003F8276602d_003FFromQueryAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIImageProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F75d84f6ed6ffc68dfa26feab5f9bdd2c3f7f18b1dcb31ba7c17723a9153b49a8_003FIImageProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AILibraryManager_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F8bee64d50df207991e33a95fceb50d15369caee29874fb919b40bd18b110_003FILibraryManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImageType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Faa567582df2a4613e0c695037928b7acde5365d9d183effaf9e4967c3afe7a_003FImageType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInternalItemsQuery_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fbac53285d850e394b99e61339968486d86d458dd56b6c8d1c97ecbb76f9d_003FInternalItemsQuery_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIRemoteImageProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F7c968f2245c52ff6a421bb307de4de67d52a456e767bd121d5d49850f546_003FIRemoteImageProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AItemLookupInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fa5624d02a21bf3271b26759b2f385a3b2499af4928c4a6e93a2de366d3ce_003FItemLookupInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMovie_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F1e53cde0ae407da5e0d8b3675976ee156dbd2d3335bf707cb079be17fa471_003FMovie_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANullable_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F14a2b70486934722acea07b38045413ab2d200_003F0f_003F279f6d62_003FNullable_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASeasonInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F653f529ca112f3656e34d9b45946f33439b3e98cf773ffae38919541ff9c46_003FSeasonInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASeason_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fb5676b1ae3e5e98ca3e1d2fa9e7a9b5f9a9c220ece6ebde1a6bcb72c9b7f01f_003FSeason_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASeries_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff148e65fdb43480ab36e867827ff357283000_003F66_003F515d9cd6_003FSeries_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASeries_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F9db9f2cea9e2b9d4e437349fabd6fc7b228d6fb2ff6f24d9367b75b06ae53_003FSeries_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASR_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F14a2b70486934722acea07b38045413ab2d200_003Fc9_003F5772db0a_003FSR_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F14a2b70486934722acea07b38045413ab2d200_003F8f_003F6d1224a3_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary> \ No newline at end of file
diff --git a/ImageProvider.cs b/ImageProvider.cs
index 8274a27..94f2598 100644
--- a/ImageProvider.cs
+++ b/ImageProvider.cs
@@ -68,8 +68,8 @@ public class ImageProvider
var imageInfo = new RemoteImageInfo
{
Url = file.downloadUrl,
- ProviderName = set.user_created.username + "(from Mediux)",
- ThumbnailUrl = file.downloadUrl,
+ ProviderName = set.user_created.username + " (via Mediux)",
+ ThumbnailUrl = file.downloadUrl, // TODO: use generated thumbnails from /_next/image?url=
Language = "en",
RatingType = RatingType.Likes,
Type = ft.Value
diff --git a/POJO.cs b/POJO.cs
index 7f44d53..dcc91a0 100644
--- a/POJO.cs
+++ b/POJO.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
@@ -15,6 +16,17 @@ public class POJO
[JsonIgnore] public IEnumerable<Set> allSets => sets.Concat(collectionSets);
}
+ public class SetData
+ {
+ public Set set { get; set; }
+ }
+
+ public class ShowData
+ {
+ // public Show show { get; set; }
+ public List<Set> sets { get; set; }
+ }
+
public class Set
{
public string id { get; set; }
@@ -23,6 +35,29 @@ public class POJO
public User user_created { get; set; }
public List<File> files { get; set; }
+ public Show? show { get; set; }
+ }
+
+ public class Show
+ {
+ public string id { get; set; }
+ public string name { get; set; }
+ public List<Season> seasons { get; set; }
+ }
+
+ public class Season
+ {
+ public string id { get; set; }
+ public int season_number { get; set; }
+ public string name { get; set; }
+ public List<Episode> episodes { get; set; }
+ }
+
+ public class Episode
+ {
+ public int episode_number { get; set; }
+ public string episode_name { get; set; }
+ public string id { get; set; }
}
public class User
@@ -30,11 +65,17 @@ public class POJO
public string username { get; set; }
}
+ public class EpisodeId
+ {
+ public string id { get; set; }
+ }
+
public class File
{
public string fileType { get; set; }
public string title { get; set; }
public string id { get; set; }
+ public EpisodeId? episode_id { get; set; }
public ImageType? JellyFinFileType()
{
@@ -43,6 +84,7 @@ public class POJO
case "backdrop":
return ImageType.Backdrop;
case "poster":
+ case "title_card":
return ImageType.Primary;
}
diff --git a/SeriesImageProvider.cs b/SeriesImageProvider.cs
index 7f48223..dd0f3b3 100644
--- a/SeriesImageProvider.cs
+++ b/SeriesImageProvider.cs
@@ -1,14 +1,18 @@
-using Microsoft.Extensions.Logging;
+using System.Linq;
namespace Jellyfin.Plugin.JCoverXtremePro;
using System.Collections.Generic;
using System.Net.Http;
+using System.Text.Json;
+using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Plugin.JCoverXtremePro.Api;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
@@ -26,13 +30,14 @@ public class SeriesImageProvider
{
return
[
+ // Note: update JCoverSharedController if more image types are supported
ImageType.Primary
];
}
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{
- // TODO: hadnle episodes
+ // TODO: handle specific episodes directly
if (item is Series series)
{
return await HandleSeries(series, cancellationToken);
@@ -51,8 +56,20 @@ public class SeriesImageProvider
var metadata = await MediuxDownloader.instance.GetMediuxMetadata("https://mediux.pro/shows/" + tmdbId)
.ConfigureAwait(false);
- Plugin.Logger.LogInformation("JSON: " + metadata);
- return [];
+ var show = JsonSerializer.Deserialize<POJO.ShowData>(metadata as JsonObject)!;
+
+ return from set in show.sets
+ let representativeImage = set.files.Find(it => it.fileType is "poster" or "title_card")!
+ let enrichedUrl = JCoverSharedController.PackSetInfo(representativeImage.downloadUrl, series, set)
+ select new RemoteImageInfo
+ {
+ Url = enrichedUrl,
+ ProviderName = set.user_created.username + " (via Mediux)",
+ ThumbnailUrl = enrichedUrl, // TODO: use generated thumbnails from /_next/image?url=
+ Language = "en",
+ RatingType = RatingType.Likes,
+ Type = representativeImage.JellyFinFileType().Value
+ };
}
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)