Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<None Remove="Configuration\configPage.html" />
<EmbeddedResource Include="Configuration\configPage.html" />
<EmbeddedResource Include="Configuration\visualizer.js" />
<EmbeddedResource Include="Configuration\inject.js" />
<EmbeddedResource Include="Configuration\version.txt" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Net.Mime;
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
using MediaBrowser.Controller.Entities.TV;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
Expand Down Expand Up @@ -104,4 +105,16 @@ public ActionResult<List<IntroWithMetadata>> GetAllIntros()

return intros;
}

/// <summary>
/// Gets the user interface configuration.
/// </summary>
/// <response code="200">UserInterfaceConfiguration returned.</response>
/// <returns>UserInterfaceConfiguration.</returns>
[Route("Intros/UserInterfaceConfiguration")]
public ActionResult<UserInterfaceConfiguration> GetUserInterfaceConfiguration()
{
var config = Plugin.Instance!.Configuration;
return new UserInterfaceConfiguration(config.SkipButtonVisible, config.SkipButtonText);
}
}
117 changes: 98 additions & 19 deletions ConfusedPolarBear.Plugin.IntroSkipper/Plugin.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
Expand Down Expand Up @@ -47,9 +48,11 @@ public Plugin(
_libraryManager = libraryManager;
_logger = logger;

FingerprintCachePath = Path.Join(applicationPaths.PluginConfigurationsPath, "intros", "cache");
FFmpegPath = serverConfiguration.GetEncodingOptions().EncoderAppPathDisplay;
_introPath = Path.Join(applicationPaths.PluginConfigurationsPath, "intros", "intros.xml");

var introsDirectory = Path.Join(applicationPaths.PluginConfigurationsPath, "intros");
FingerprintCachePath = Path.Join(introsDirectory, "cache");
_introPath = Path.Join(introsDirectory, "intros.xml");

// Create the base & cache directories (if needed).
if (!Directory.Exists(FingerprintCachePath))
Expand All @@ -68,6 +71,35 @@ public Plugin(
{
_logger.LogWarning("Unable to load introduction timestamps: {Exception}", ex);
}

// Inject the skip intro button code into the web interface.
var indexPath = Path.Join(applicationPaths.WebPath, "index.html");
try
{
InjectSkipButton(indexPath, Path.Join(introsDirectory, "index-pre-skip-button.html"));
}
catch (Exception ex)
{
// WarningManager.SetFlag(PluginWarning.UnableToAddSkipButton);

if (ex is UnauthorizedAccessException)
{
var suggestion = OperatingSystem.IsLinux() ?
"running `sudo chown jellyfin PATH` (if this is a native installation)" :
"changing the permissions of PATH";

suggestion = suggestion.Replace("PATH", indexPath, StringComparison.Ordinal);

_logger.LogError(
"Failed to add skip button to web interface. Try {Suggestion} and restarting the server. Error: {Error}",
suggestion,
ex);
}
else
{
_logger.LogError("Unknown error encountered while adding skip button: {Error}", ex);
}
}
}

/// <summary>
Expand Down Expand Up @@ -148,6 +180,29 @@ public void RestoreTimestamps()
}
}

/// <inheritdoc />
public IEnumerable<PluginPageInfo> GetPages()
{
return new[]
{
new PluginPageInfo
{
Name = this.Name,
EmbeddedResourcePath = GetType().Namespace + ".Configuration.configPage.html"
},
new PluginPageInfo
{
Name = "visualizer.js",
EmbeddedResourcePath = GetType().Namespace + ".Configuration.visualizer.js"
},
new PluginPageInfo
{
Name = "skip-intro-button.js",
EmbeddedResourcePath = GetType().Namespace + ".Configuration.inject.js"
}
};
}

internal BaseItem GetItem(Guid id)
{
return _libraryManager.GetItemById(id);
Expand Down Expand Up @@ -176,26 +231,50 @@ internal void UpdateTimestamps(Dictionary<Guid, Intro> newIntros)
}
}

/// <inheritdoc />
public IEnumerable<PluginPageInfo> GetPages()
private void OnConfigurationChanged(object? sender, BasePluginConfiguration e)
{
return new[]
{
new PluginPageInfo
{
Name = this.Name,
EmbeddedResourcePath = GetType().Namespace + ".Configuration.configPage.html"
},
new PluginPageInfo
{
Name = "visualizer.js",
EmbeddedResourcePath = GetType().Namespace + ".Configuration.visualizer.js"
}
};
AutoSkipChanged?.Invoke(this, EventArgs.Empty);
}

private void OnConfigurationChanged(object? sender, BasePluginConfiguration e)
/// <summary>
/// Inject the skip button script into the web interface.
/// </summary>
/// <param name="indexPath">Full path to index.html.</param>
/// <param name="backupPath">Full path to create a backup of index.html at.</param>
private void InjectSkipButton(string indexPath, string backupPath)
{
AutoSkipChanged?.Invoke(this, EventArgs.Empty);
// Parts of this code are based off of JellyScrub's script injection code.
// https://github.com/nicknsy/jellyscrub/blob/4ce806f602988a662cfe3cdbaac35ee8046b7ec4/Nick.Plugin.Jellyscrub/JellyscrubPlugin.cs

_logger.LogInformation("Adding skip button to {Path}", indexPath);

_logger.LogDebug("Reading index.html from {Path}", indexPath);
var contents = File.ReadAllText(indexPath);
_logger.LogDebug("Successfully read index.html");

var scriptTag = "<script src=\"configurationpage?name=skip-intro-button.js\"></script>";

// Only inject the script tag once
if (contents.Contains(scriptTag, StringComparison.OrdinalIgnoreCase))
{
_logger.LogInformation("Skip button already added");
return;
}

// Backup the original version of the web interface
_logger.LogInformation("Backing up index.html to {Backup}", backupPath);
File.WriteAllText(backupPath, contents);

// Inject a link to the script at the end of the <head> section.
// A regex is used here to ensure the replacement is only done once.
_logger.LogDebug("Injecting script tag");
var headEnd = new Regex("</head>", RegexOptions.IgnoreCase);
contents = headEnd.Replace(contents, scriptTag + "</head>", 1);

// Write the modified file contents
_logger.LogDebug("Saving modified file");
File.WriteAllText(indexPath, contents);

_logger.LogInformation("Skip intro button successfully added to web interface");
}
}