2024-10-25 14:31:50 -04:00
// Copyright (C) 2024 Intro-Skipper contributors <intro-skipper.org>
// SPDX-License-Identifier: GPL-3.0-only.
2024-10-25 14:15:12 -04:00
2019-02-21 01:57:43 -08:00
using System ;
2024-05-08 16:27:16 +02:00
using System.Collections.Concurrent ;
2019-02-21 01:57:43 -08:00
using System.Collections.Generic ;
2022-05-05 18:10:34 -05:00
using System.IO ;
2024-09-20 14:18:04 +03:00
using System.Linq ;
2022-11-06 21:20:52 -06:00
using System.Text.RegularExpressions ;
2024-11-02 18:17:22 +01:00
using System.Threading.Tasks ;
2024-10-20 00:03:59 +02:00
using System.Xml ;
using System.Xml.Serialization ;
2024-10-19 23:50:41 +02:00
using IntroSkipper.Configuration ;
using IntroSkipper.Data ;
2024-11-02 18:17:22 +01:00
using IntroSkipper.Db ;
2024-10-19 23:50:41 +02:00
using IntroSkipper.Helper ;
2019-02-21 01:57:43 -08:00
using MediaBrowser.Common.Configuration ;
using MediaBrowser.Common.Plugins ;
2022-06-09 14:07:40 -05:00
using MediaBrowser.Controller.Configuration ;
2022-07-29 03:34:55 -05:00
using MediaBrowser.Controller.Entities ;
2022-06-15 01:00:03 -05:00
using MediaBrowser.Controller.Library ;
2022-11-24 00:43:23 -06:00
using MediaBrowser.Controller.Persistence ;
using MediaBrowser.Model.Entities ;
2019-02-21 01:57:43 -08:00
using MediaBrowser.Model.Plugins ;
using MediaBrowser.Model.Serialization ;
2024-10-12 16:13:37 +02:00
using MediaBrowser.Model.Updates ;
2024-11-02 18:17:22 +01:00
using Microsoft.EntityFrameworkCore ;
2022-09-27 20:31:18 -05:00
using Microsoft.Extensions.Logging ;
2019-02-21 01:57:43 -08:00
2024-10-19 23:50:41 +02:00
namespace IntroSkipper ;
2021-12-13 16:58:05 -07:00
/// <summary>
2022-05-01 01:24:57 -05:00
/// Intro skipper plugin. Uses audio analysis to find common sequences of audio shared between episodes.
2021-12-13 16:58:05 -07:00
/// </summary>
2024-09-25 17:23:25 +02:00
public class Plugin : BasePlugin < PluginConfiguration > , IHasWebPages
2019-02-21 01:57:43 -08:00
{
2024-09-10 18:08:42 +02:00
private readonly ILibraryManager _libraryManager ;
private readonly IItemRepository _itemRepository ;
private readonly ILogger < Plugin > _logger ;
private readonly string _introPath ;
private readonly string _creditsPath ;
2024-11-02 18:17:22 +01:00
private readonly string _dbPath ;
2022-05-05 18:10:34 -05:00
2022-05-01 01:24:57 -05:00
/// <summary>
/// Initializes a new instance of the <see cref="Plugin"/> class.
/// </summary>
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
2022-06-09 14:07:40 -05:00
/// <param name="serverConfiguration">Server configuration manager.</param>
2022-06-15 01:00:03 -05:00
/// <param name="libraryManager">Library manager.</param>
2022-11-24 00:43:23 -06:00
/// <param name="itemRepository">Item repository.</param>
2022-09-27 20:31:18 -05:00
/// <param name="logger">Logger.</param>
2022-06-09 14:07:40 -05:00
public Plugin (
IApplicationPaths applicationPaths ,
IXmlSerializer xmlSerializer ,
2022-06-15 01:00:03 -05:00
IServerConfigurationManager serverConfiguration ,
2022-09-27 20:31:18 -05:00
ILibraryManager libraryManager ,
2022-11-24 00:43:23 -06:00
IItemRepository itemRepository ,
2022-09-27 20:31:18 -05:00
ILogger < Plugin > logger )
2022-05-01 01:24:57 -05:00
: base ( applicationPaths , xmlSerializer )
{
2022-09-27 21:03:27 -05:00
Instance = this ;
2022-06-15 01:00:03 -05:00
_libraryManager = libraryManager ;
2022-11-24 00:43:23 -06:00
_itemRepository = itemRepository ;
2022-09-27 20:31:18 -05:00
_logger = logger ;
2022-05-05 18:10:34 -05:00
2022-09-27 21:03:27 -05:00
FFmpegPath = serverConfiguration . GetEncodingOptions ( ) . EncoderAppPathDisplay ;
2022-11-06 21:20:52 -06:00
2024-03-29 15:32:23 +01:00
ArgumentNullException . ThrowIfNull ( applicationPaths ) ;
2024-05-16 18:24:36 +02:00
var pluginDirName = "introskipper" ;
var pluginCachePath = "chromaprints" ;
2024-03-18 19:52:57 +01:00
2024-05-16 18:24:36 +02:00
var introsDirectory = Path . Join ( applicationPaths . DataPath , pluginDirName ) ;
FingerprintCachePath = Path . Join ( introsDirectory , pluginCachePath ) ;
_introPath = Path . Join ( applicationPaths . DataPath , pluginDirName , "intros.xml" ) ;
_creditsPath = Path . Join ( applicationPaths . DataPath , pluginDirName , "credits.xml" ) ;
2024-11-02 18:17:22 +01:00
_dbPath = Path . Join ( applicationPaths . DataPath , pluginDirName , "introskipper.db" ) ;
2024-05-16 18:24:36 +02:00
2024-10-07 07:44:07 +02:00
// Create the base & cache directories (if needed).
if ( ! Directory . Exists ( FingerprintCachePath ) )
{
Directory . CreateDirectory ( FingerprintCachePath ) ;
}
2024-03-29 15:32:23 +01:00
// migrate from XMLSchema to DataContract
XmlSerializationHelper . MigrateXML ( _introPath ) ;
XmlSerializationHelper . MigrateXML ( _creditsPath ) ;
2024-10-20 00:03:59 +02:00
var oldConfigFile = Path . Join ( applicationPaths . PluginConfigurationsPath , "ConfusedPolarBear.Plugin.IntroSkipper.xml" ) ;
if ( File . Exists ( oldConfigFile ) )
{
try
{
XmlSerializer serializer = new XmlSerializer ( typeof ( PluginConfiguration ) ) ;
2024-11-02 18:17:22 +01:00
using FileStream fileStream = new FileStream ( oldConfigFile , FileMode . Open ) ;
var settings = new XmlReaderSettings
2024-10-20 00:03:59 +02:00
{
2024-11-02 18:17:22 +01:00
DtdProcessing = DtdProcessing . Prohibit , // Disable DTD processing
XmlResolver = null // Disable the XmlResolver
} ;
2024-10-20 00:03:59 +02:00
2024-11-02 18:17:22 +01:00
using var reader = XmlReader . Create ( fileStream , settings ) ;
if ( serializer . Deserialize ( reader ) is PluginConfiguration oldConfig )
{
Instance . UpdateConfiguration ( oldConfig ) ;
2024-11-05 20:04:27 +01:00
fileStream . Close ( ) ;
2024-11-02 18:17:22 +01:00
File . Delete ( oldConfigFile ) ;
2024-10-20 00:03:59 +02:00
}
}
catch ( Exception ex )
{
// Handle exceptions, such as file not found, deserialization errors, etc.
2024-10-30 20:37:03 +01:00
_logger . LogWarning ( "Something stupid happened: {Exception}" , ex ) ;
2024-10-20 00:03:59 +02:00
}
}
2024-10-27 22:38:46 +01:00
MigrateRepoUrl ( serverConfiguration ) ;
2024-11-02 18:17:22 +01:00
// Initialize database, restore timestamps if available.
2024-09-20 14:18:04 +03:00
try
{
2024-11-02 18:17:22 +01:00
using var db = new IntroSkipperDbContext ( _dbPath ) ;
db . ApplyMigrations ( ) ;
if ( File . Exists ( _introPath ) | | File . Exists ( _creditsPath ) )
{
2024-11-21 15:42:55 +01:00
RestoreTimestamps ( ) ;
2024-11-02 18:17:22 +01:00
}
2024-09-20 14:18:04 +03:00
}
catch ( Exception ex )
{
2024-11-02 18:17:22 +01:00
_logger . LogWarning ( "Error initializing database: {Exception}" , ex ) ;
2024-09-20 14:18:04 +03:00
}
2022-11-06 21:20:52 -06:00
// Inject the skip intro button code into the web interface.
try
{
2024-08-31 17:08:42 +00:00
InjectSkipButton ( applicationPaths . WebPath ) ;
2022-11-06 21:20:52 -06:00
}
catch ( Exception ex )
{
2022-11-06 21:25:23 -06:00
WarningManager . SetFlag ( PluginWarning . UnableToAddSkipButton ) ;
2022-11-06 21:20:52 -06:00
2024-09-19 13:06:37 +02:00
_logger . LogError ( "Failed to add skip button to web interface. See https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#skip-button-is-not-visible for the most common issues. Error: {Error}" , ex ) ;
2022-11-06 21:20:52 -06:00
}
2024-03-07 17:08:15 -05:00
FFmpegWrapper . CheckFFmpegVersion ( ) ;
2022-05-05 18:10:34 -05:00
}
2022-05-09 22:56:03 -05:00
/// <summary>
2024-11-02 18:17:22 +01:00
/// Gets the path to the database.
2022-05-09 22:56:03 -05:00
/// </summary>
2024-11-02 18:17:22 +01:00
public string DbPath = > _dbPath ;
2022-11-24 00:43:23 -06:00
2024-11-21 15:42:55 +01:00
/// <summary>
/// Gets or sets a value indicating whether to analyze again.
/// </summary>
public bool AnalyzeAgain { get ; set ; }
2022-05-09 22:56:03 -05:00
/// <summary>
2022-11-23 02:34:28 -06:00
/// Gets the most recent media item queue.
2022-05-09 22:56:03 -05:00
/// </summary>
2024-05-08 16:27:16 +02:00
public ConcurrentDictionary < Guid , List < QueuedEpisode > > QueuedMediaItems { get ; } = new ( ) ;
2022-05-09 22:56:03 -05:00
/// <summary>
/// Gets or sets the total number of episodes in the queue.
/// </summary>
public int TotalQueued { get ; set ; }
2023-06-08 00:51:18 -05:00
/// <summary>
/// Gets or sets the number of seasons in the queue.
/// </summary>
public int TotalSeasons { get ; set ; }
2022-05-09 22:56:03 -05:00
/// <summary>
/// Gets the directory to cache fingerprints in.
/// </summary>
public string FingerprintCachePath { get ; private set ; }
2022-06-09 14:07:40 -05:00
/// <summary>
/// Gets the full path to FFmpeg.
/// </summary>
public string FFmpegPath { get ; private set ; }
2022-05-09 22:56:03 -05:00
/// <inheritdoc />
public override string Name = > "Intro Skipper" ;
/// <inheritdoc />
public override Guid Id = > Guid . Parse ( "c83d86bb-a1e0-4c35-a113-e2101cf4ee6b" ) ;
/// <summary>
/// Gets the plugin instance.
/// </summary>
public static Plugin ? Instance { get ; private set ; }
2022-05-05 18:10:34 -05:00
/// <summary>
/// Restore previous analysis results from disk.
/// </summary>
2024-11-21 15:42:55 +01:00
public void RestoreTimestamps ( )
2022-05-05 18:10:34 -05:00
{
2024-11-21 15:42:55 +01:00
using var db = new IntroSkipperDbContext ( _dbPath ) ;
2024-11-02 18:17:22 +01:00
// Import intros
2022-11-24 00:43:23 -06:00
if ( File . Exists ( _introPath ) )
2022-05-05 18:10:34 -05:00
{
2024-09-20 14:18:04 +03:00
var introList = XmlSerializationHelper . DeserializeFromXml < Segment > ( _introPath ) ;
2022-11-24 00:43:23 -06:00
foreach ( var intro in introList )
{
2024-11-21 15:42:55 +01:00
db . DbSegment . Add ( new DbSegment ( intro , AnalysisMode . Introduction ) ) ;
2022-11-24 00:43:23 -06:00
}
}
2022-05-05 18:10:34 -05:00
2024-11-02 18:17:22 +01:00
// Import credits
2022-11-24 00:43:23 -06:00
if ( File . Exists ( _creditsPath ) )
2022-05-05 18:10:34 -05:00
{
2024-09-20 14:18:04 +03:00
var creditList = XmlSerializationHelper . DeserializeFromXml < Segment > ( _creditsPath ) ;
2022-11-24 00:43:23 -06:00
foreach ( var credit in creditList )
{
2024-11-21 15:42:55 +01:00
db . DbSegment . Add ( new DbSegment ( credit , AnalysisMode . Credits ) ) ;
2022-11-24 00:43:23 -06:00
}
2022-05-05 18:10:34 -05:00
}
2024-11-02 18:17:22 +01:00
2024-11-21 15:42:55 +01:00
db . SaveChanges ( ) ;
2024-11-02 18:17:22 +01:00
File . Delete ( _introPath ) ;
File . Delete ( _creditsPath ) ;
2022-05-05 18:10:34 -05:00
}
2022-11-06 21:20:52 -06:00
/// <inheritdoc />
public IEnumerable < PluginPageInfo > GetPages ( )
{
2024-09-10 18:08:42 +02:00
return
[
2022-11-06 21:20:52 -06:00
new PluginPageInfo
{
2024-09-10 18:08:42 +02:00
Name = Name ,
2022-11-06 21:20:52 -06:00
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"
}
2024-09-10 18:08:42 +02:00
] ;
2022-11-06 21:20:52 -06:00
}
2024-05-01 13:45:57 +02:00
internal BaseItem ? GetItem ( Guid id )
2022-07-29 03:34:55 -05:00
{
2024-10-05 19:30:30 +02:00
return id ! = Guid . Empty ? _libraryManager . GetItemById ( id ) : null ;
2022-07-29 03:34:55 -05:00
}
2024-11-21 15:42:55 +01:00
internal ICollection < Folder > GetCollectionFolders ( Guid id )
2024-09-21 20:12:00 +02:00
{
var item = GetItem ( id ) ;
return item is not null ? _libraryManager . GetCollectionFolders ( item ) : [ ] ;
}
2022-06-15 01:00:03 -05:00
/// <summary>
/// Gets the full path for an item.
/// </summary>
/// <param name="id">Item id.</param>
/// <returns>Full path to item.</returns>
internal string GetItemPath ( Guid id )
{
2024-05-01 13:45:57 +02:00
var item = GetItem ( id ) ;
if ( item = = null )
{
// Handle the case where the item is not found
_logger . LogWarning ( "Item with ID {Id} not found." , id ) ;
return string . Empty ;
}
return item . Path ;
2022-06-15 01:00:03 -05:00
}
2022-11-24 00:43:23 -06:00
/// <summary>
/// Gets all chapters for this item.
/// </summary>
/// <param name="id">Item id.</param>
/// <returns>List of chapters.</returns>
2024-10-09 18:33:41 +02:00
internal IReadOnlyList < ChapterInfo > GetChapters ( Guid id )
2022-10-28 02:25:57 -05:00
{
2024-05-01 13:45:57 +02:00
var item = GetItem ( id ) ;
if ( item = = null )
{
// Handle the case where the item is not found
_logger . LogWarning ( "Item with ID {Id} not found." , id ) ;
2024-09-10 18:08:42 +02:00
return [ ] ;
2024-05-01 13:45:57 +02:00
}
return _itemRepository . GetChapters ( item ) ;
2022-11-24 00:43:23 -06:00
}
2024-11-21 15:42:55 +01:00
internal async Task UpdateTimestampAsync ( Segment segment , AnalysisMode mode )
2022-10-28 02:25:57 -05:00
{
2024-11-02 18:17:22 +01:00
using var db = new IntroSkipperDbContext ( _dbPath ) ;
2024-11-21 15:42:55 +01:00
try
2022-10-28 02:25:57 -05:00
{
2024-11-21 15:42:55 +01:00
var existing = await db . DbSegment
. FirstOrDefaultAsync ( s = > s . ItemId = = segment . EpisodeId & & s . Type = = mode )
. ConfigureAwait ( false ) ;
var dbSegment = new DbSegment ( segment , mode ) ;
if ( existing is not null )
2022-10-28 02:25:57 -05:00
{
2024-11-21 15:42:55 +01:00
db . Entry ( existing ) . CurrentValues . SetValues ( dbSegment ) ;
2024-05-08 16:27:16 +02:00
}
2024-11-02 18:17:22 +01:00
else
2024-05-08 16:27:16 +02:00
{
2024-11-21 15:42:55 +01:00
db . DbSegment . Add ( dbSegment ) ;
2022-10-28 02:25:57 -05:00
}
2024-05-08 16:27:16 +02:00
2024-11-21 15:42:55 +01:00
await db . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Failed to update timestamp for episode {EpisodeId}" , segment . EpisodeId ) ;
throw ;
}
2024-11-02 18:17:22 +01:00
}
2024-11-21 15:42:55 +01:00
internal IReadOnlyDictionary < AnalysisMode , Segment > GetTimestamps ( Guid id )
2024-11-02 18:17:22 +01:00
{
using var db = new IntroSkipperDbContext ( _dbPath ) ;
2024-11-21 15:42:55 +01:00
return db . DbSegment . Where ( s = > s . ItemId = = id )
. ToDictionary ( s = > s . Type , s = > s . ToSegment ( ) ) ;
2024-11-02 18:17:22 +01:00
}
2024-11-21 15:42:55 +01:00
internal async Task CleanTimestamps ( IEnumerable < Guid > episodeIds )
2024-11-02 18:17:22 +01:00
{
using var db = new IntroSkipperDbContext ( _dbPath ) ;
db . DbSegment . RemoveRange ( db . DbSegment
. Where ( s = > ! episodeIds . Contains ( s . ItemId ) ) ) ;
await db . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
2022-10-28 02:25:57 -05:00
}
2024-11-21 15:42:55 +01:00
internal async Task SetAnalyzerActionAsync ( Guid id , IReadOnlyDictionary < AnalysisMode , AnalyzerAction > analyzerActions )
2024-11-02 18:17:22 +01:00
{
using var db = new IntroSkipperDbContext ( _dbPath ) ;
var existingEntries = await db . DbSeasonInfo
. Where ( s = > s . SeasonId = = id )
. ToDictionaryAsync ( s = > s . Type )
. ConfigureAwait ( false ) ;
foreach ( var ( mode , action ) in analyzerActions )
2024-06-15 10:57:20 +02:00
{
2024-11-02 18:17:22 +01:00
if ( existingEntries . TryGetValue ( mode , out var existing ) )
{
2024-11-21 15:42:55 +01:00
db . Entry ( existing ) . Property ( s = > s . Action ) . CurrentValue = action ;
2024-11-02 18:17:22 +01:00
}
else
2024-06-15 10:57:20 +02:00
{
2024-11-21 15:42:55 +01:00
db . DbSeasonInfo . Add ( new DbSeasonInfo ( id , mode , action ) ) ;
2024-06-15 10:57:20 +02:00
}
}
2024-11-02 18:17:22 +01:00
await db . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
}
2024-11-21 15:42:55 +01:00
internal async Task SetEpisodeIdsAsync ( Guid id , AnalysisMode mode , IEnumerable < Guid > episodeIds )
{
using var db = new IntroSkipperDbContext ( _dbPath ) ;
var seasonInfo = db . DbSeasonInfo . FirstOrDefault ( s = > s . SeasonId = = id & & s . Type = = mode ) ;
if ( seasonInfo is null )
{
seasonInfo = new DbSeasonInfo ( id , mode , AnalyzerAction . Default , episodeIds ) ;
db . DbSeasonInfo . Add ( seasonInfo ) ;
}
else
{
db . Entry ( seasonInfo ) . Property ( s = > s . EpisodeIds ) . CurrentValue = episodeIds ;
}
await db . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
}
internal IReadOnlyDictionary < AnalysisMode , IEnumerable < Guid > > GetEpisodeIds ( Guid id )
2024-11-02 18:17:22 +01:00
{
using var db = new IntroSkipperDbContext ( _dbPath ) ;
2024-11-21 15:42:55 +01:00
return db . DbSeasonInfo . Where ( s = > s . SeasonId = = id )
. ToDictionary ( s = > s . Type , s = > s . EpisodeIds ) ;
2024-11-02 18:17:22 +01:00
}
internal AnalyzerAction GetAnalyzerAction ( Guid id , AnalysisMode mode )
{
using var db = new IntroSkipperDbContext ( _dbPath ) ;
return db . DbSeasonInfo . FirstOrDefault ( s = > s . SeasonId = = id & & s . Type = = mode ) ? . Action ? ? AnalyzerAction . Default ;
}
2024-11-21 15:42:55 +01:00
internal async Task CleanSeasonInfoAsync ( IEnumerable < Guid > ids )
2024-11-02 18:17:22 +01:00
{
using var db = new IntroSkipperDbContext ( _dbPath ) ;
var obsoleteSeasons = await db . DbSeasonInfo
2024-11-21 15:42:55 +01:00
. Where ( s = > ! ids . Contains ( s . SeasonId ) )
2024-11-02 18:17:22 +01:00
. ToListAsync ( ) . ConfigureAwait ( false ) ;
db . DbSeasonInfo . RemoveRange ( obsoleteSeasons ) ;
await db . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
2024-06-15 10:57:20 +02:00
}
2024-10-12 16:13:37 +02:00
private void MigrateRepoUrl ( IServerConfigurationManager serverConfiguration )
{
try
{
List < string > oldRepos =
[
"https://raw.githubusercontent.com/intro-skipper/intro-skipper/master/manifest.json" ,
2024-10-31 11:58:56 +01:00
"https://raw.githubusercontent.com/jumoog/intro-skipper/master/manifest.json" ,
"https://manifest.intro-skipper.workers.dev/manifest.json"
2024-10-12 16:13:37 +02:00
] ;
// Access the current server configuration
var config = serverConfiguration . Configuration ;
// Get the list of current plugin repositories
2024-10-27 22:38:46 +01:00
var pluginRepositories = config . PluginRepositories . ToList ( ) ;
2024-10-12 16:13:37 +02:00
// check if old plugins exits
2024-10-27 22:38:46 +01:00
if ( pluginRepositories . Exists ( repo = > repo . Url ! = null & & oldRepos . Contains ( repo . Url ) ) )
2024-10-12 16:13:37 +02:00
{
// remove all old plugins
2024-10-27 22:38:46 +01:00
pluginRepositories . RemoveAll ( repo = > repo . Url ! = null & & oldRepos . Contains ( repo . Url ) ) ;
2024-10-12 16:13:37 +02:00
2024-10-27 22:38:46 +01:00
// Add repository only if it does not exit and the OverideManifestUrl Option is activated
if ( ! pluginRepositories . Exists ( repo = > repo . Url = = "https://manifest.intro-skipper.org/manifest.json" ) & & Instance ! . Configuration . OverrideManifestUrl )
2024-10-12 16:13:37 +02:00
{
// Add the new repository to the list
pluginRepositories . Add ( new RepositoryInfo
{
Name = "intro skipper (automatically migrated by plugin)" ,
2024-10-24 22:48:12 +02:00
Url = "https://manifest.intro-skipper.org/manifest.json" ,
2024-10-12 16:13:37 +02:00
Enabled = true ,
} ) ;
}
// Update the configuration with the new repository list
config . PluginRepositories = [ . . pluginRepositories ] ;
// Save the updated configuration
serverConfiguration . SaveConfiguration ( ) ;
}
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error occurred while migrating repo URL" ) ;
}
}
2022-11-06 21:20:52 -06:00
/// <summary>
/// Inject the skip button script into the web interface.
/// </summary>
2024-08-31 17:08:42 +00:00
/// <param name="webPath">Full path to index.html.</param>
private void InjectSkipButton ( string webPath )
2022-06-14 14:36:05 -05:00
{
2024-11-08 11:01:15 +01:00
string pattern ;
2024-08-31 17:08:42 +00:00
// Inject the skip intro button code into the web interface.
string indexPath = Path . Join ( webPath , "index.html" ) ;
2022-11-06 21:20:52 -06:00
// Parts of this code are based off of JellyScrub's script injection code.
2024-03-03 21:46:52 -05:00
// https://github.com/nicknsy/jellyscrub/blob/main/Nick.Plugin.Jellyscrub/JellyscrubPlugin.cs#L38
2022-11-06 21:20:52 -06:00
_logger . LogDebug ( "Reading index.html from {Path}" , indexPath ) ;
2024-08-31 17:08:42 +00:00
string contents = File . ReadAllText ( indexPath ) ;
2022-11-06 21:20:52 -06:00
2024-10-27 05:08:22 -04:00
if ( ! Instance ! . Configuration . SkipButtonEnabled )
2024-10-12 06:35:46 -04:00
{
pattern = @"<script src=""configurationpage\?name=skip-intro-button\.js.*<\/script>" ;
2024-10-27 22:38:46 +01:00
if ( ! Regex . IsMatch ( contents , pattern , RegexOptions . IgnoreCase ) )
{
return ;
}
2024-10-12 06:35:46 -04:00
contents = Regex . Replace ( contents , pattern , string . Empty , RegexOptions . IgnoreCase ) ;
File . WriteAllText ( indexPath , contents ) ;
return ; // Button is disabled, so remove and abort
}
2024-10-27 22:38:46 +01:00
// change URL with every release to prevent the Browsers from caching
2024-08-31 17:08:42 +00:00
string scriptTag = "<script src=\"configurationpage?name=skip-intro-button.js&release=" + GetType ( ) . Assembly . GetName ( ) . Version + "\"></script>" ;
2022-11-06 21:20:52 -06:00
// Only inject the script tag once
if ( contents . Contains ( scriptTag , StringComparison . OrdinalIgnoreCase ) )
{
2024-10-05 13:17:30 +02:00
_logger . LogInformation ( "The skip button has already been injected." ) ;
2022-11-06 21:20:52 -06:00
return ;
}
2024-07-30 21:22:09 +02:00
// remove old version if necessary
2024-10-05 18:38:42 +02:00
pattern = @"<script src=""configurationpage\?name=skip-intro-button\.js.*<\/script>" ;
2024-07-30 21:22:09 +02:00
contents = Regex . Replace ( contents , pattern , string . Empty , RegexOptions . IgnoreCase ) ;
2022-11-06 21:20:52 -06:00
// 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.
2024-09-25 17:23:25 +02:00
Regex headEnd = new Regex ( @"</head>" , RegexOptions . IgnoreCase , TimeSpan . FromSeconds ( 1 ) ) ;
2022-11-06 21:20:52 -06:00
contents = headEnd . Replace ( contents , scriptTag + "</head>" , 1 ) ;
// Write the modified file contents
File . WriteAllText ( indexPath , contents ) ;
2024-10-05 13:17:30 +02:00
_logger . LogInformation ( "Skip button added successfully." ) ;
2022-06-14 14:36:05 -05:00
}
2019-03-10 08:53:30 +09:00
}