From 5c1cbd9c4e29aded66f00399293caeddafa970a7 Mon Sep 17 00:00:00 2001 From: HighImKevin Date: Mon, 9 Feb 2026 00:11:21 -0500 Subject: [PATCH] Load KefinTweaks scripts which don't require login/admin before users login --- configuration/homeScreen-configuration.js | 7 +- injector.js | 99 +++++++++++++++-------- kefinTweaks-plugin.js | 6 +- scripts/userHelper.js | 39 +++++++-- scripts/watchlist.js | 4 +- 5 files changed, 112 insertions(+), 43 deletions(-) diff --git a/configuration/homeScreen-configuration.js b/configuration/homeScreen-configuration.js index 378c23b..3164378 100644 --- a/configuration/homeScreen-configuration.js +++ b/configuration/homeScreen-configuration.js @@ -348,6 +348,11 @@ */ async function verifyRecentlyAddedInLibraryConfig(config) { try { + if (!window.ApiClient || !window.ApiClient._loggedIn) { + WARN('User not logged in, skipping recently-added verification'); + return config; + } + if (!window.dataHelper || !window.dataHelper.getLibraries) { WARN('dataHelper.getLibraries not available, skipping recently-added verification'); return config; @@ -2179,7 +2184,7 @@
${buildSelect(`query-${queryIndex}-parentItemType`, [ - { value: '', label: 'Select a Parent...' }, + { value: '', label: 'Select a Parent... (optional)' }, { value: 'Collection', label: 'Collection' }, { value: 'Playlist', label: 'Playlist' }, { value: 'Generic Parent', label: 'Generic Parent' } diff --git a/injector.js b/injector.js index 4cfc1c8..d50c966 100644 --- a/injector.js +++ b/injector.js @@ -19,37 +19,42 @@ return "Pending Install"; // Fallback } - // Match version patterns: @commitHash, @latest, @main, or @v0.3.3 - // Note: check for hash first to avoid partial numeric matches (e.g. hash starting with digits) - const versionMatch = root.match(/@([a-f0-9]{7,}|latest|main|v?[\d.]+)/i); - - if (versionMatch) { - const version = versionMatch[1]; + // Match version patterns: @commitHash, @latest, @main, @experimental, or @v0.3.3 + // Note: check for hash first to avoid partial numeric matches (e.g. hash starting with digits) + const versionMatch = root.match(/@([a-f0-9]{7,}|latest|main|experimental|v?[\d.]+)/i); - // Handle version tags (v0.3.3 -> 0.3.3) - if (version.startsWith('v') && /^\d+\.\d+\.\d+/.test(version.substring(1))) { - return version.substring(1); // Remove 'v' prefix + if (versionMatch) { + const version = versionMatch[1]; + + // Handle version tags (v0.3.3 -> 0.3.3) + if (version.startsWith('v') && /^\d+\.\d+\.\d+/.test(version.substring(1))) { + return version.substring(1); // Remove 'v' prefix + } + + // Handle @main -> "development" + if (version.toLowerCase() === 'main') { + return 'development'; + } + + // Handle @experimental -> "experimental" + if (version.toLowerCase() === 'experimental') { + return 'experimental'; + } + + // Handle @latest -> "latest" + if (version.toLowerCase() === 'latest') { + return 'latest'; + } + + // Handle commit hash -> show as "Dev (#hash)" + if (/^[a-f0-9]{7,}$/i.test(version)) { + return `Dev (#${version.substring(0, 7)})`; + } + + // Return as-is for other patterns + return version; } - // Handle @main -> "Dev" - if (version.toLowerCase() === 'main') { - return 'development'; - } - - // Handle @latest -> "latest" - if (version.toLowerCase() === 'latest') { - return 'latest'; - } - - // Handle commit hash -> show as "Dev (#hash)" - if (/^[a-f0-9]{7,}$/i.test(version)) { - return `Dev (#${version.substring(0, 7)})`; - } - - // Return as-is for other patterns - return version; - } - // No version found in URL, this is a custom install return "Custom Install"; } @@ -75,6 +80,8 @@ seriesInfo: true, // Add series and season information to details pages collections: true, // Collection sorting functionality skinManager: true, // Skin selection and management + thumbnailScrubber: false, // Trickplay thumbnail scrub on hover (bottom 20px of video cards) + // Note: Core functionality scripts (utils, cardBuilder, localStorageCache, modal) // are automatically enabled when needed by other scripts @@ -285,7 +292,7 @@ name: 'homeScreen', script: 'homeScreen3.js', css: 'homeScreen.css', - dependencies: ['cardBuilder', 'localStorageCache', 'utils', 'homeScreenConfig2', 'homeScreen-configuration', 'peopleCache', 'studiosCache', 'moviesCache', 'indexedDBCache', 'homeScreenConfigCommunity', 'dataHelper', 'apiHelper', 'websocketHelper', 'homeScreen-migration', 'homeScreen-user-configuration'], + dependencies: ['cardBuilder', 'localStorageCache', 'utils', 'homeScreenConfig2', 'homeScreen-configuration', 'peopleCache', 'studiosCache', 'moviesCache', 'indexedDBCache', 'homeScreenConfigCommunity', 'dataHelper', 'apiHelper', 'homeScreen-migration', 'homeScreen-user-configuration'], priority: true, // Load immediately after dependencies to reduce UI disruption description: 'Adds custom home screen sections' }, @@ -331,6 +338,13 @@ dependencies: [], description: 'Fixes the dashboard button to redirect to the home page when the back button is clicked and there is no history to go back to' }, + { + name: 'thumbnailScrubber', + script: 'thumbnailScrubber.js', + css: null, + dependencies: [], + description: 'Shows trickplay thumbnail preview when hovering in the bottom 20px of a video card for 2+ seconds' + }, { name: 'infiniteScroll', script: 'infiniteScroll.js', @@ -509,11 +523,12 @@ return true; } - // Resolve @latest or @main to actual version/commit hash + // Resolve @latest, @main, or @experimental to actual version/commit hash (so user always loads latest from that ref) async function resolveRootVersion(root) { - // Check if root contains @latest or @main + // Check if root contains @latest, @main, or @experimental const latestMatch = root.match(/@latest(\/|$)/); const mainMatch = root.match(/@main(\/|$)/); + const experimentalMatch = root.match(/@experimental(\/|$)/); if (latestMatch) { // Fetch the latest release version number @@ -556,6 +571,26 @@ console.warn('[KefinTweaks Injector] Error fetching commit hash:', error); return root; } + } else if (experimentalMatch) { + // Fetch the latest commit hash from the experimental branch + try { + const response = await fetch('https://api.github.com/repos/ranaldsgift/KefinTweaks/commits/experimental'); + if (response.ok) { + const commit = await response.json(); + if (commit && commit.sha) { + return root.replace('@experimental', `@${commit.sha}`); + } else { + console.warn('[KefinTweaks Injector] Could not fetch experimental commit hash, using @experimental'); + return root; + } + } else { + console.warn('[KefinTweaks Injector] Failed to fetch experimental commit hash, using @experimental'); + return root; + } + } catch (error) { + console.warn('[KefinTweaks Injector] Error fetching experimental commit hash:', error); + return root; + } } // No resolution needed @@ -575,7 +610,7 @@ // Ensure root ends with / let root = kefinTweaksRoot.endsWith('/') ? kefinTweaksRoot : kefinTweaksRoot + '/'; - // Resolve @latest or @main if needed (with caching) + // Resolve @latest, @main, or @experimental if needed (with caching) if (resolvedRootCache === null) { root = await resolveRootVersion(root); resolvedRootCache = root; diff --git a/kefinTweaks-plugin.js b/kefinTweaks-plugin.js index e971e34..725c9db 100644 --- a/kefinTweaks-plugin.js +++ b/kefinTweaks-plugin.js @@ -1329,6 +1329,9 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`; const maxRetries = 100; // 10 seconds (100 * 100ms) try { + // Check if there is a valid config with a root url already, and load the injector if so + checkAndLoadInjector(); + // Check if ApiClient is available and user is logged in if (!window.ApiClient || !window.ApiClient._loggedIn) { if (retryCount < maxRetries) { @@ -1342,9 +1345,6 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`; } } - // Check if there is a valid config with a root url already, and load the injector if so - checkAndLoadInjector(); - // Check if user is admin const userIsAdmin = await isAdmin(); if (!userIsAdmin) { diff --git a/scripts/userHelper.js b/scripts/userHelper.js index 376e857..c0981ee 100644 --- a/scripts/userHelper.js +++ b/scripts/userHelper.js @@ -13,6 +13,28 @@ throw new Error('ApiClient is not available'); } } + + function ensureLoggedIn() { + if (typeof ApiClient === 'undefined' || !ApiClient) { + throw new Error('ApiClient is not available'); + } + if (!ApiClient._loggedIn) { + throw new Error('User is not logged in'); + } + } + + // Run this on a separate thread to avoid blocking the main thread with requestIdleCallback + async function waitForLogin() { + return new Promise((resolve) => { + requestIdleCallback(() => { + if (window.ApiClient && window.ApiClient._loggedIn) { + resolve(true); + } else { + setTimeout(() => waitForLogin(), 100); + } + }); + }); + } /** * API Helper functions for Jellyfin operations @@ -20,6 +42,7 @@ const userHelper = { getUserDisplayPreferences: async function() { ensureApiClient(); + ensureLoggedIn(); let cachedDisplayPreferences = null; @@ -63,13 +86,16 @@ return { promise: userDisplayPreferencesPromise, cached: cachedDisplayPreferences }; }, useEpisodeImages: async function() { - const { promise, cached } = await this.getUserDisplayPreferences(); - const userDisplayPreferences = await promise; - if (!userDisplayPreferences) { - LOG('No user display preferences found'); + try { + ensureApiClient(); + ensureLoggedIn(); + const { promise, cached } = await this.getUserDisplayPreferences(); + const userDisplayPreferences = await promise; + return userDisplayPreferences.CustomPrefs?.useEpisodeImagesInNextUpAndResume === 'true'; + } catch (error) { + WARN('Failed to load user display preferences for useEpisodeImages:', error); return false; } - return userDisplayPreferences.CustomPrefs?.useEpisodeImagesInNextUpAndResume === 'true'; }, updateCustomPrefsByKey: async function(key, value) { const { promise, cached } = await this.getUserDisplayPreferences(); @@ -96,7 +122,8 @@ body: JSON.stringify(prefs) }); return response.ok; - } + }, + waitForLogin: waitForLogin }; // TODO -- Update this whenever the user display preferences are updated diff --git a/scripts/watchlist.js b/scripts/watchlist.js index e59acfe..049d7ee 100644 --- a/scripts/watchlist.js +++ b/scripts/watchlist.js @@ -4616,11 +4616,13 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: } // Initialize monitoring to remove watched items from watchlist (playback + manual) - function initializePlaybackMonitoring() { + async function initializePlaybackMonitoring() { if (playbackMonitorInitialized) { return; } + await window.userHelper.waitForLogin(); + const maxRetries = 10; let retries = 0; // Use WebSocket to listen for UserDataChanged messages instead of intercepting fetch