Load KefinTweaks scripts which don't require login/admin before users login

This commit is contained in:
HighImKevin
2026-02-09 00:11:21 -05:00
parent 7bd28d8380
commit 5c1cbd9c4e
5 changed files with 112 additions and 43 deletions
+6 -1
View File
@@ -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 @@
<div style="display: grid; gap: 0.75em;">
<!-- Parent Item Dropdown -->
${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' }
+67 -32
View File
@@ -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;
+3 -3
View File
@@ -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) {
+33 -6
View File
@@ -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
+3 -1
View File
@@ -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