From b25f572ef4bbaef7e9cbc3730eda4b581a6df067 Mon Sep 17 00:00:00 2001 From: HighImKevin Date: Tue, 6 Oct 2026 22:25:51 -0400 Subject: [PATCH] Fix not syncing native Jellyfin home sections when using the "Update" function from KefinTweaks Home Screen configuration --- configuration/homeScreen-configuration.js | 30 +++++ .../homeScreen-user-configuration.js | 125 +++++++++++++----- 2 files changed, 122 insertions(+), 33 deletions(-) diff --git a/configuration/homeScreen-configuration.js b/configuration/homeScreen-configuration.js index 8ac7f45..9dc5793 100644 --- a/configuration/homeScreen-configuration.js +++ b/configuration/homeScreen-configuration.js @@ -3889,6 +3889,20 @@ window.KefinTweaksConfig = ${JSON.stringify(configToBackup, null, 2)};`; }); } + function getServerEnabledSectionsForUpdatePreview() { + const config = currentConfig || getConfig(); + if (!config) return []; + const inSeason = (s) => (s.startDate && s.endDate ? isInSeasonalPeriod(s.startDate, s.endDate) : true); + const enabledFrom = (groups) => flattenSectionGroups(groups || []).filter( + (s) => s.enabled === true && s.discoveryEnabled !== true && inSeason(s) + ); + return [ + ...enabledFrom(config.HOME_SECTION_GROUPS), + ...enabledFrom(config.SEASONAL_SECTION_GROUPS), + ...enabledFrom(config.CUSTOM_SECTION_GROUPS) + ]; + } + function confirmAndUpdateAllUsers() { if (!window.ModalSystem) { ERR('ModalSystem not available for update confirmation'); @@ -3898,11 +3912,27 @@ window.KefinTweaksConfig = ${JSON.stringify(configToBackup, null, 2)};`; window.ModalSystem.close(UPDATE_ALL_CONFIRM_MODAL_ID); } + // Reflect current order UI before previewing native slots + try { + applyOrderListFromForm(mainModalInstance?.dialogContent); + } catch (_) { /* preview best-effort */ } + + const serverEnabled = getServerEnabledSectionsForUpdatePreview(); + const slots = window.KefinUserHomeScreenConfig?.buildHomesectionSlotsFromKefin?.(serverEnabled) || []; + const pairedSlotPreview = slots.filter((id) => id && id !== 'none').join(', ') || '(none)'; + const content = document.createElement('div'); content.innerHTML = `
WARNING: This will forcefully update the KefinTweaks Home Screen user preferences for all accounts on your server. Any changes those users have made to customize their home screen will be lost.
+
+ Users with Pair Home Sections on will get these native Jellyfin slots: + ${pairedSlotPreview} +
+
+ Users with Pair Home Sections off get all native slots set to none. +
`; const footer = document.createElement('div'); footer.style.display = 'flex'; diff --git a/configuration/homeScreen-user-configuration.js b/configuration/homeScreen-user-configuration.js index f44856e..39174ab 100644 --- a/configuration/homeScreen-user-configuration.js +++ b/configuration/homeScreen-user-configuration.js @@ -862,10 +862,28 @@ } /** - * Clear all section overrides for every user (pins preserved). + * Resolve admin-enabled server catalog sections used to pack native homesectionN on Update. + */ + function getServerEnabledSectionsForNativeSync() { + const config = window.KefinHomeScreen?.getConfig?.(); + if (Array.isArray(config?.ENABLED_NORMAL_SECTIONS)) { + return config.ENABLED_NORMAL_SECTIONS.filter((s) => s?.enabled === true); + } + const sectionsApi = window.KefinHomeScreen?.getSections; + if (typeof sectionsApi === 'function') { + const { enabledHomeSections = [] } = sectionsApi() || {}; + return (enabledHomeSections || []).filter((s) => s?.enabled === true); + } + return []; + } + + /** + * Clear all section overrides for every user (pins preserved) and rewrite homesection0–9 + * from the server-enabled catalog (honors each user's pairNativeHomeSections). * @param {{ onProgress?: Function }} [options] */ async function updateUserHomeScreenConfiguration(options = {}) { + const serverEnabledSections = getServerEnabledSectionsForNativeSync(); const users = await listServerUsers(); const total = users.length; let done = 0; @@ -876,14 +894,23 @@ const userName = user.Name || user.name || userId; try { const prefs = await window.userHelper.getUserDisplayPreferencesForUser(userId); - const homeScreen = parseKefinTweaksHomeScreen(prefs?.CustomPrefs); - if ((homeScreen.sections || []).length > 0) { - homeScreen.sections = []; + if (!prefs.CustomPrefs) prefs.CustomPrefs = {}; + const homeScreen = parseKefinTweaksHomeScreen(prefs.CustomPrefs); + const hadOverrides = (homeScreen.sections || []).length > 0; + homeScreen.sections = []; + + const jellyfinSync = syncNativeHomeSectionsFromKefin( + serverEnabledSections, + prefs.CustomPrefs, + homeScreen + ); + + if (hadOverrides || jellyfinSync.changed) { await saveHomeScreenForUser(userId, homeScreen, prefs); } } catch (e) { failed += 1; - WARN(`Failed clearing homeScreen sections for ${userName}:`, e); + WARN(`Failed updating homeScreen / native sections for ${userName}:`, e); } done += 1; options.onProgress?.({ done, total, failed, userName }); @@ -894,19 +921,55 @@ } /** - * Resolve Jellyfin pairing id for a catalog section. - * Prefer section.jellyfinId; fall back to map / recently-added-* pattern. + * All Jellyfin pairing ids for a catalog section (jellyfinId + jellyfinId2). + * Falls back to map / recently-added-* when neither is set. + * @returns {string[]} lowercased unique non-empty ids (excludes 'none') + */ + function getSectionJellyfinIds(section) { + if (!section) return []; + const ids = []; + const push = (value) => { + if (value == null || value === '') return; + const normalized = String(value).toLowerCase(); + if (!normalized || normalized === 'none') return; + if (!ids.includes(normalized)) ids.push(normalized); + }; + + push(section.jellyfinId); + push(section.jellyfinId2); + + if (!ids.length) { + const id = section.id || ''; + if (id.startsWith('recently-added-')) { + push('latestmedia'); + } else { + for (const [jellyfinId, kefinId] of Object.entries(JELLYFIN_HOME_SECTIONS_MAP)) { + if (jellyfinId === 'latestmedia' && id.startsWith('recently-added-')) { + push('latestmedia'); + break; + } + if (kefinId === id) { + push(jellyfinId); + break; + } + } + } + } + return ids; + } + + function sectionMatchesNativeType(section, nativeType) { + const type = String(nativeType || '').toLowerCase(); + if (!type || type === 'none') return false; + return getSectionJellyfinIds(section).includes(type); + } + + /** + * Primary Jellyfin pairing id for a catalog section (first of getSectionJellyfinIds). */ function resolveSectionJellyfinId(section) { - if (!section) return null; - if (section.jellyfinId) return String(section.jellyfinId).toLowerCase(); - const id = section.id || ''; - if (id.startsWith('recently-added-')) return 'latestmedia'; - for (const [jellyfinId, kefinId] of Object.entries(JELLYFIN_HOME_SECTIONS_MAP)) { - if (jellyfinId === 'latestmedia' && id.startsWith('recently-added-')) return 'latestmedia'; - if (kefinId === id) return String(jellyfinId).toLowerCase(); - } - return null; + const ids = getSectionJellyfinIds(section); + return ids[0] || null; } /** @@ -918,15 +981,10 @@ const collected = []; (sections || []).forEach((section) => { if (section.enabled !== true) return; - const jellyfinId = resolveSectionJellyfinId(section); - if (!jellyfinId || jellyfinId === 'none') return; - const order = section.order || 0; - collected.push({ jellyfinId, order }); - - if (section.id === 'continueWatchingAndNextUp') { - collected.push({ jellyfinId: 'nextup', order }); - } + getSectionJellyfinIds(section).forEach((jellyfinId) => { + collected.push({ jellyfinId, order }); + }); }); collected.sort((a, b) => (a.order || 0) - (b.order || 0)); @@ -975,26 +1033,21 @@ /** * Candidate Kefin sections for a native homesection type (inbound enable sync). - * resume → continueWatching only; livetv/latestmedia → all matches. + * Matches jellyfinId or jellyfinId2 (e.g. resume/nextup both hit continueWatchingAndNextUp). + * latestmedia also includes recently-added-* rows. */ function resolveKefinCandidatesForNative(nativeType, catalogSections) { const type = String(nativeType || '').toLowerCase(); if (!type || type === 'none') return []; const catalog = catalogSections || []; - if (type === 'resume') { - return catalog.filter((s) => s?.id === 'continueWatching'); - } if (type === 'latestmedia') { return catalog.filter((s) => { const id = s?.id || ''; - return id.startsWith('recently-added-') || resolveSectionJellyfinId(s) === 'latestmedia'; + return id.startsWith('recently-added-') || sectionMatchesNativeType(s, 'latestmedia'); }); } - if (type === 'livetv') { - return catalog.filter((s) => resolveSectionJellyfinId(s) === 'livetv'); - } - return catalog.filter((s) => resolveSectionJellyfinId(s) === type); + return catalog.filter((s) => sectionMatchesNativeType(s, type)); } const RECENTLY_ADDED_PREFIX = 'recently-added-'; @@ -1267,9 +1320,13 @@ uniqueNative.forEach((nativeType) => { const candidates = resolveKefinCandidatesForNative(nativeType, catalog); if (!candidates.length) return; + // Covered if any candidate is already on (includes continueWatchingAndNextUp) if (candidates.some(sectionIsEnabled)) return; candidates.forEach((section) => { + // Never auto-enable the combined Continue Watching + Next Up row + if (section.id === 'continueWatchingAndNextUp') return; + const storedId = getStoredSectionPrefId(section); const existing = prefMap.get(getPrefDedupeKey({ id: section.id })) || prefMap.get(getPrefDedupeKey({ id: storedId })) @@ -2761,6 +2818,8 @@ resolvePairNativeHomeSections, getServerPairNativeHomeSectionsDefault, buildHomesectionSlotsFromKefin, + getSectionJellyfinIds, + sectionMatchesNativeType, resolveSectionJellyfinId, resolveHomesectionValue, DEFAULT_NATIVE_HOME_SECTIONS,