diff options
| author | Shadowghost <Shadowghost@users.noreply.github.com> | 2026-09-27 16:30:43 -0400 |
|---|---|---|
| committer | Cody Robibero <cody@robibe.ro> | 2026-09-27 16:30:43 -0400 |
| commit | dcc7b6dde98fb3015ded1f725c91080314d4037e (patch) | |
| tree | a09f88370b01258656aa2f8411313202adb7c570 | |
| parent | b1787cbc36d44d7feccd7e9adfadf739a56cc190 (diff) | |
Backport pull request #18096 from jellyfin/release-12.z
Decode HTML entities in OMDb responses
Original-merge: 8bd7306fc9aeff8996f474f65647ebde7d001577
Merged-by: crobibero <cody@robibe.ro>
Backported-by: Cody Robibero <cody@robibe.ro>
4 files changed, 54 insertions, 10 deletions
diff --git a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs index 7e9befa8c6..0278469807 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using System.Text.Json; using System.Text.Json.Serialization; using Jellyfin.Extensions.Json; @@ -6,7 +7,7 @@ using Jellyfin.Extensions.Json; namespace MediaBrowser.Providers.Plugins.Omdb { /// <summary> - /// Converts a string <c>N/A</c> to <c>string.Empty</c>. + /// Converts a string <c>N/A</c> to <c>string.Empty</c> and decodes HTML entities in every other string. /// </summary> public class JsonOmdbNotAvailableStringConverter : JsonConverter<string?> { @@ -27,7 +28,9 @@ namespace MediaBrowser.Providers.Plugins.Omdb return null; } - return str; + // Some OMDb records are HTML encoded, e.g. the cast of tt0093058 lists "Vincent D'Onofrio". + // Stored verbatim that name is a second person next to the correctly spelled one. + return WebUtility.HtmlDecode(str); } return JsonSerializer.Deserialize<string?>(ref reader, options); diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index 7b245ea5a7..c81ada3e00 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -13,7 +13,6 @@ using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Extensions.Json; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -45,9 +44,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb _libraryManager = libraryManager; _omdbProvider = new OmdbProvider(_httpClientFactory, fileSystem, configurationManager); - _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options); - _jsonOptions.Converters.Add(new JsonOmdbNotAvailableStringConverter()); - _jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter()); + _jsonOptions = OmdbProvider.CreateJsonOptions(); } public string Name => "The Open Movie Database"; diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index d51d913caa..60e4001189 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -45,10 +45,19 @@ namespace MediaBrowser.Providers.Plugins.Omdb _fileSystem = fileSystem; _configurationManager = configurationManager; - _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options); - // These converters need to take priority - _jsonOptions.Converters.Insert(0, new JsonOmdbNotAvailableStringConverter()); - _jsonOptions.Converters.Insert(0, new JsonOmdbNotAvailableInt32Converter()); + _jsonOptions = CreateJsonOptions(); + } + + /// <summary>Creates the <see cref="JsonSerializerOptions"/> to read OMDB responses with.</summary> + /// <returns>The options.</returns> + internal static JsonSerializerOptions CreateJsonOptions() + { + var options = new JsonSerializerOptions(JsonDefaults.Options); + // These have to be inserted, appending leaves the default string converter ahead of them + options.Converters.Insert(0, new JsonOmdbNotAvailableStringConverter()); + options.Converters.Insert(0, new JsonOmdbNotAvailableInt32Converter()); + + return options; } /// <summary>Fetches data from OMDB service.</summary> diff --git a/tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs b/tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs index 3062cb7b42..0275d13242 100644 --- a/tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs +++ b/tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs @@ -63,6 +63,41 @@ namespace Jellyfin.Providers.Tests.Omdb Assert.Equal(expected, result); } + [Theory] + [InlineData("\"Vincent D'Onofrio\"", "Vincent D'Onofrio")] + [InlineData("\"Alan Moore 'Swamp Thing'\"", "Alan Moore 'Swamp Thing'")] + [InlineData("\"Tom & Jerry\"", "Tom & Jerry")] + [InlineData("\"Mail <a@b.com> for a translation\"", "Mail <a@b.com> for a translation")] + [InlineData("\"Elisa Mont\\u00e9s\"", "Elisa Montés")] + [InlineData("\"R&D and 100% & more\"", "R&D and 100% & more")] + public void Deserialize_Html_Encoded_String_Is_Decoded(string input, string expected) + { + var result = JsonSerializer.Deserialize<string?>(input, _options); + Assert.Equal(expected, result); + } + + [Fact] + public void Deserialize_Html_Encoded_Credits_Are_Decoded() + { + const string Input = "{\"Title\":\"Full Metal Jacket\",\"Actors\":\"Matthew Modine, R. Lee Ermey, Vincent D'Onofrio\",\"Response\":\"True\"}"; + var rootObject = JsonSerializer.Deserialize<OmdbProvider.RootObject>(Input, _options); + Assert.NotNull(rootObject); + Assert.Equal("Matthew Modine, R. Lee Ermey, Vincent D'Onofrio", rootObject!.Actors); + } + + [Fact] + public void ProviderJsonOptions_Take_Priority_Over_The_Defaults() + { + // The default string converter handles string as well, so appending the OMDb ones would never run them + var options = OmdbProvider.CreateJsonOptions(); + + const string Input = "{\"Title\":\"Full Metal Jacket\",\"Actors\":\"Vincent D'Onofrio\",\"Awards\":\"N/A\",\"Response\":\"True\"}"; + var rootObject = JsonSerializer.Deserialize<OmdbProvider.RootObject>(Input, options); + Assert.NotNull(rootObject); + Assert.Equal("Vincent D'Onofrio", rootObject!.Actors); + Assert.Null(rootObject.Awards); + } + [Fact] public void Roundtrip_Valid_Success() { |
