From f60e3645e18a7f590dc913d0f555fcb82f072d07 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Sun, 11 Aug 2024 21:47:46 +0200 Subject: init --- .gitignore | 7 +++ Configuration/PluginConfiguration.cs | 57 +++++++++++++++++ Configuration/configPage.html | 80 ++++++++++++++++++++++++ ImageProvider.cs | 101 ++++++++++++++++++++++++++++++ Jellyfin.Plugin.JCoverXtremePro.csproj | 29 +++++++++ MediuxDownloader.cs | 85 +++++++++++++++++++++++++ POJO.cs | 48 +++++++++++++++ Plugin.cs | 51 +++++++++++++++ jellyfin.ruleset | 109 +++++++++++++++++++++++++++++++++ 9 files changed, 567 insertions(+) create mode 100644 .gitignore create mode 100644 Configuration/PluginConfiguration.cs create mode 100644 Configuration/configPage.html create mode 100644 ImageProvider.cs create mode 100644 Jellyfin.Plugin.JCoverXtremePro.csproj create mode 100644 MediuxDownloader.cs create mode 100644 POJO.cs create mode 100644 Plugin.cs create mode 100644 jellyfin.ruleset diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b393798 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +bin +obj +.idea/ + + + + diff --git a/Configuration/PluginConfiguration.cs b/Configuration/PluginConfiguration.cs new file mode 100644 index 0000000..8268858 --- /dev/null +++ b/Configuration/PluginConfiguration.cs @@ -0,0 +1,57 @@ +using MediaBrowser.Model.Plugins; + +namespace Jellyfin.Plugin.JellyFed.Configuration; + +/// +/// The configuration options. +/// +public enum SomeOptions +{ + /// + /// Option one. + /// + OneOption, + + /// + /// Second option. + /// + AnotherOption +} + +/// +/// Plugin configuration. +/// +public class PluginConfiguration : BasePluginConfiguration +{ + /// + /// Initializes a new instance of the class. + /// + public PluginConfiguration() + { + // set default options here + Options = SomeOptions.AnotherOption; + TrueFalseSetting = true; + AnInteger = 2; + AString = "string"; + } + + /// + /// Gets or sets a value indicating whether some true or false setting is enabled. + /// + public bool TrueFalseSetting { get; set; } + + /// + /// Gets or sets an integer setting. + /// + public int AnInteger { get; set; } + + /// + /// Gets or sets a string setting. + /// + public string AString { get; set; } + + /// + /// Gets or sets an enum option. + /// + public SomeOptions Options { get; set; } +} \ No newline at end of file diff --git a/Configuration/configPage.html b/Configuration/configPage.html new file mode 100644 index 0000000..b72df9f --- /dev/null +++ b/Configuration/configPage.html @@ -0,0 +1,80 @@ + + + + + Template + + +
+
+
+
+
+ + +
+
+ + +
A Description
+
+
+ +
+
+ + +
Another Description
+
+
+ +
+
+
+
+ +
+ + diff --git a/ImageProvider.cs b/ImageProvider.cs new file mode 100644 index 0000000..df349eb --- /dev/null +++ b/ImageProvider.cs @@ -0,0 +1,101 @@ +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 MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Model.Dto; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Plugin.JCoverXtremePro; + +public class ImageProvider + : IRemoteImageProvider, IHasOrder +{ + private ILogger _logger; + + + public ImageProvider(ILogger logger) + { + _logger = logger; + } + + public bool Supports(BaseItem item) + { + return item is Movie; + } + + public string Name => "Mediux"; + + public IEnumerable GetSupportedImages(BaseItem item) + { + return new List + { + ImageType.Primary, + // ImageType.Backdrop, + }; + } + + public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + { + var movie = item as Movie; + var movieId = item.GetProviderId(MetadataProvider.Tmdb); + var collectionId = item.GetProviderId(MetadataProvider.TmdbCollection); + _logger.LogInformation( + $"Help i am stuck in a movie labeling factory and have been taskted to label {movie.Name} " + + $"({movieId} in collection {collectionId})" + ); + var movieData = + await MediuxDownloader.instance.GetMediuxMetadata("https://mediux.pro/movies/" + movieId) + .ConfigureAwait(false); + var deserMovieData = JsonSerializer.Deserialize(movieData as JsonObject); + _logger.LogInformation("Movie Data: {JsonData}", movieData.ToJsonString()); + _logger.LogInformation("Movie Data Decoded: {Data}", JsonSerializer.Serialize(deserMovieData)); + List images = new(); + foreach (var set in deserMovieData.allSets) + { + _logger.LogInformation("Set Data: {Name} {Data}", set.set_name, set.files.Count); + foreach (var file in set.files) + { + _logger.LogInformation("Matching file {Name}", JsonSerializer.Serialize(file)); + if (file.fileType != "poster") + { + _logger.LogInformation("Skipping non poster file"); + continue; + } + + if (file.title.Contains(deserMovieData.movie.title)) + { + _logger.LogInformation("Adding image"); + var imageInfo = new RemoteImageInfo + { + Url = file.downloadUrl, + ProviderName = Name, + ThumbnailUrl = file.downloadUrl, + Language = "en", + RatingType = RatingType.Likes, + Type = ImageType.Primary, + }; + _logger.LogInformation("Constructed image"); + images.Add(imageInfo); + _logger.LogInformation("Appended image"); + } + } + } + + _logger.LogInformation("Collected images {0}", images); + return images; + } + + public int Order => 0; + + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + return MediuxDownloader.instance.DownloadFile(url); + } +} \ No newline at end of file diff --git a/Jellyfin.Plugin.JCoverXtremePro.csproj b/Jellyfin.Plugin.JCoverXtremePro.csproj new file mode 100644 index 0000000..8455ca0 --- /dev/null +++ b/Jellyfin.Plugin.JCoverXtremePro.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + Jellyfin.Plugin.JCoverXtremePro + true + false + enable + AllEnabledByDefault + ./jellyfin.ruleset + + + + + + + + + + + + + + + + + + + diff --git a/MediuxDownloader.cs b/MediuxDownloader.cs new file mode 100644 index 0000000..a027773 --- /dev/null +++ b/MediuxDownloader.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Plugin.JCoverXtremePro; + +public class MediuxDownloader +{ + public static MediuxDownloader instance; + + private Regex contentRegex = new(@"]*>self\.__next_f\.push(.*?)"); + private readonly HttpClient httpClientFactory; + private string sentinel = "date"; + + public MediuxDownloader(IHttpClientFactory httpClientFactory) + { + this.httpClientFactory = httpClientFactory.CreateClient("MediuxDownloader"); + } + + private List ExtractJsonNodes(string httpText) + { + List list = new(); + foreach (Match match in contentRegex.Matches(httpText)) + { + var pushArg = match.Groups[1].Value; + var strippedString = StripPushArg(pushArg); + if (!strippedString.Contains(sentinel)) + { + Plugin.Logger.LogTrace("Ignoring chunk without sentinel {Sentinel}: {Chunk}", sentinel, strippedString); + continue; + } + + list.Add(ParseStrippedJsonChunk(strippedString)); + } + + if (list.Count != 1) + { + Plugin.Logger.LogError("Found too many or too few chunks: {0}", list); + } + + return list; + } + + private JsonNode ParseStrippedJsonChunk(string text) + { + return JsonSerializer.Deserialize(text.Substring(text.IndexOf(':') + 1))[3]; + } + + private string StripPushArg(string text) + { + var stringStart = text.IndexOf('"'); + var stringEnd = text.LastIndexOf('"'); + if (stringStart == stringEnd || stringStart == -1) + { + return ""; + } + + // TODO: 1 is regular data, 3 is base64 partial data + return JsonSerializer.Deserialize(text.Substring(stringStart, stringEnd + 1 - stringStart)) ?? ""; + } + + private async Task GetString(string url) + { + return await (await httpClientFactory.GetAsync(url).ConfigureAwait(false)) + .Content.ReadAsStringAsync().ConfigureAwait(false); + } + + public async Task GetMediuxMetadata(string url) + { + Plugin.Logger.LogInformation("Loading data from {Url}", url); + var text = await GetString(url).ConfigureAwait(false); + return ExtractJsonNodes(text).First(); + } + + public async Task DownloadFile(string url) + { + return await httpClientFactory.GetAsync(url).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/POJO.cs b/POJO.cs new file mode 100644 index 0000000..a19a98e --- /dev/null +++ b/POJO.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; + +namespace Jellyfin.Plugin.JCoverXtremePro; + +public class POJO +{ + public class MovieData + { + public Movie movie { get; set; } + public List sets { get; set; } + public List collectionSets { get; set; } + [JsonIgnore] public IEnumerable allSets => sets.Concat(collectionSets); + } + + public class Set + { + public string id { get; set; } + + public string set_name { get; set; } + + public User user_created { get; set; } + public List files { get; set; } + } + + public class User + { + public string username { get; set; } + } + + public class File + { + public string fileType { get; set; } + public string title { get; set; } + public string id { get; set; } + + [JsonIgnore] public string downloadUrl => "https://api.mediux.pro/assets/" + id; + } + + public class Movie + { + public string id { get; set; } + public string title { get; set; } + public string tagline { get; set; } + public string imdb_id { get; set; } + } +} \ No newline at end of file diff --git a/Plugin.cs b/Plugin.cs new file mode 100644 index 0000000..a4e1f6b --- /dev/null +++ b/Plugin.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net.Http; +using Jellyfin.Plugin.JellyFed.Configuration; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Plugin.JCoverXtremePro; + +public class Plugin : BasePlugin, IHasWebPages +{ + public Plugin( + IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, + ILibraryManager libraryManager, ILogger logger, + IHttpClientFactory httpClientFactory + ) : base(applicationPaths, xmlSerializer) + { + logger.LogInformation("Loaded plugin with library manager {}", libraryManager); + MediuxDownloader.instance = new MediuxDownloader(httpClientFactory); + Instance = this; + Logger = logger; + } + + public override string Name => "JCoverXtremePro"; + + public override Guid Id => Guid.Parse("f3e43e23-4b28-4b2f-a29d-37267e2ea2e2"); + + public static Plugin? Instance { get; private set; } + + public static ILogger Logger { get; private set; } + + /// + public IEnumerable GetPages() + { + return new[] + { + new PluginPageInfo + { + Name = Name, + EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Configuration.configPage.html", + GetType().Namespace) + } + }; + } +} \ No newline at end of file diff --git a/jellyfin.ruleset b/jellyfin.ruleset new file mode 100644 index 0000000..9f7bf2a --- /dev/null +++ b/jellyfin.ruleset @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit