mirror of
https://github.com/ranaldsgift/KefinTweaks.git
synced 2026-11-05 15:52:47 +01:00
727 lines
33 KiB
JavaScript
727 lines
33 KiB
JavaScript
// KefinTweaks HomeScreen Configuration Migration
|
|
// Migrates legacy KefinTweaksConfig.homeScreen to new KefinTweaksConfig.homeScreenConfig format
|
|
(function() {
|
|
'use strict';
|
|
|
|
const LOG = (...args) => console.log('[KefinTweaks HomeScreen Migration]', ...args);
|
|
const WARN = (...args) => console.warn('[KefinTweaks HomeScreen Migration]', ...args);
|
|
const ERR = (...args) => console.error('[KefinTweaks HomeScreen Migration]', ...args);
|
|
|
|
/**
|
|
* Check if migration is needed and perform migration
|
|
* @returns {Promise<boolean>} True if migration was performed, false otherwise
|
|
*/
|
|
async function migrateHomeScreenConfig() {
|
|
// Check if migration is needed
|
|
if (!window.KefinTweaksConfig) {
|
|
LOG('KefinTweaksConfig not found, skipping migration');
|
|
return false;
|
|
}
|
|
|
|
// If homeScreenConfig already exists, skip migration
|
|
if (window.KefinTweaksConfig.homeScreenConfig) {
|
|
LOG('homeScreenConfig already exists, skipping migration');
|
|
return false;
|
|
}
|
|
|
|
// If homeScreen doesn't exist, nothing to migrate
|
|
if (!window.KefinTweaksConfig.homeScreen) {
|
|
LOG('No legacy homeScreen config found, skipping migration');
|
|
return false;
|
|
}
|
|
|
|
// Check if KefinHomeConfig2 defaults are available
|
|
if (!window.KefinHomeConfig2) {
|
|
WARN('KefinHomeConfig2 defaults not available, cannot perform migration');
|
|
return false;
|
|
}
|
|
|
|
LOG('Starting migration from legacy homeScreen to homeScreenConfig...');
|
|
|
|
try {
|
|
const legacyHomeScreen = window.KefinTweaksConfig.homeScreen;
|
|
const defaults = window.KefinHomeConfig2;
|
|
|
|
// Initialize new config structure with defaults
|
|
const newHomeScreenConfig = {
|
|
HOME_SECTION_GROUPS: JSON.parse(JSON.stringify(defaults.HOME_SECTION_GROUPS || [])),
|
|
SEASONAL_SECTION_GROUPS: JSON.parse(JSON.stringify(defaults.SEASONAL_SECTION_GROUPS || [])),
|
|
DISCOVERY_SECTION_GROUPS: JSON.parse(JSON.stringify(defaults.DISCOVERY_SECTION_GROUPS || [])),
|
|
CUSTOM_SECTION_GROUPS: [],
|
|
DISCOVERY_SETTINGS: JSON.parse(JSON.stringify(defaults.DISCOVERY_SETTINGS || {})),
|
|
SEASONAL_THEME_SETTINGS: JSON.parse(JSON.stringify(defaults.SEASONAL_THEME_SETTINGS || {})),
|
|
HOME_SETTINGS: JSON.parse(JSON.stringify(defaults.HOME_SETTINGS || {}))
|
|
};
|
|
|
|
// Migrate discovery settings
|
|
migrateDiscoverySettings(legacyHomeScreen, newHomeScreenConfig);
|
|
|
|
// Migrate seasonal theme settings
|
|
migrateSeasonalThemeSettings(legacyHomeScreen, newHomeScreenConfig);
|
|
|
|
// Migrate HOME sections
|
|
migrateHomeSections(legacyHomeScreen, newHomeScreenConfig);
|
|
|
|
// Migrate SEASONAL sections
|
|
migrateSeasonalSections(legacyHomeScreen, newHomeScreenConfig);
|
|
|
|
// Migrate DISCOVERY sections
|
|
migrateDiscoverySections(legacyHomeScreen, newHomeScreenConfig);
|
|
|
|
// Migrate CUSTOM sections
|
|
migrateCustomSections(legacyHomeScreen, newHomeScreenConfig);
|
|
|
|
// Save migrated config to JS Injector plugin
|
|
window.KefinTweaksConfig.homeScreenConfig = newHomeScreenConfig;
|
|
if (window.KefinTweaksUtils && window.KefinTweaksUtils.saveConfigToJavaScriptInjector) {
|
|
window.KefinTweaksUtils.saveConfigToJavaScriptInjector().catch(err => {
|
|
ERR('Failed to save migrated config to JS Injector:', err);
|
|
});
|
|
} else {
|
|
WARN('KefinTweaksUtils.saveConfigToJavaScriptInjector not available, config migration not persisted');
|
|
}
|
|
LOG('Migration completed successfully');
|
|
} catch (error) {
|
|
ERR('Migration failed:', error);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Migrate discovery settings
|
|
*/
|
|
function migrateDiscoverySettings(legacyHomeScreen, newConfig) {
|
|
if (!legacyHomeScreen.discovery) return;
|
|
|
|
const discovery = legacyHomeScreen.discovery;
|
|
const settings = newConfig.DISCOVERY_SETTINGS;
|
|
|
|
LOG('Migrating discovery settings...');
|
|
|
|
if (discovery.enabled !== undefined) settings.enabled = discovery.enabled;
|
|
if (discovery.infiniteScroll !== undefined) settings.infiniteScroll = discovery.infiniteScroll;
|
|
if (discovery.minGenreMovieCount !== undefined) settings.minGenreMovieCount = discovery.minGenreMovieCount;
|
|
if (discovery.defaultItemLimit !== undefined) settings.defaultItemLimit = discovery.defaultItemLimit;
|
|
if (discovery.defaultSortOrder !== undefined) settings.defaultSortOrder = discovery.defaultSortOrder;
|
|
if (discovery.defaultCardFormat !== undefined) settings.defaultCardFormat = discovery.defaultCardFormat;
|
|
if (discovery.spotlightDiscoveryChance !== undefined) settings.spotlightDiscoveryChance = discovery.spotlightDiscoveryChance;
|
|
if (discovery.renderSpotlightAboveMatching !== undefined) settings.renderSpotlightAboveMatching = discovery.renderSpotlightAboveMatching;
|
|
if (discovery.randomizeOrder !== undefined) settings.randomizeOrder = discovery.randomizeOrder;
|
|
if (discovery.spotlight && typeof discovery.spotlight === 'object') {
|
|
settings.spotlight = {};
|
|
Object.assign(settings.spotlight, discovery.spotlight);
|
|
}
|
|
|
|
// Legacy discovery.minPeopleAppearances → HOME_SETTINGS mins
|
|
if (discovery.minPeopleAppearances !== undefined) {
|
|
if (!newConfig.HOME_SETTINGS) newConfig.HOME_SETTINGS = {};
|
|
const min = discovery.minPeopleAppearances;
|
|
const home = newConfig.HOME_SETTINGS;
|
|
if (home.minPeopleAppearancesTotal == null) home.minPeopleAppearancesTotal = min;
|
|
if (home.minPeopleAppearancesMovies == null) home.minPeopleAppearancesMovies = min;
|
|
if (home.minPeopleAppearancesSeries == null) home.minPeopleAppearancesSeries = min;
|
|
if (home.minPeopleAppearancesEpisodes == null) home.minPeopleAppearancesEpisodes = min;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Migrate seasonal theme settings
|
|
*/
|
|
function migrateSeasonalThemeSettings(legacyHomeScreen, newConfig) {
|
|
if (!legacyHomeScreen.seasonal) return;
|
|
|
|
const seasonal = legacyHomeScreen.seasonal;
|
|
const settings = newConfig.SEASONAL_THEME_SETTINGS;
|
|
|
|
LOG('Migrating seasonal theme settings...');
|
|
|
|
if (seasonal.enabled !== undefined) settings.enabled = seasonal.enabled;
|
|
if (seasonal.enableSeasonalAnimations !== undefined) settings.enableSeasonalAnimations = seasonal.enableSeasonalAnimations;
|
|
if (seasonal.enableSeasonalBackground !== undefined) settings.enableSeasonalBackground = seasonal.enableSeasonalBackground;
|
|
}
|
|
|
|
/**
|
|
* Find section in default groups by ID
|
|
*/
|
|
function findSectionInDefaults(sectionId, groups) {
|
|
for (const group of groups) {
|
|
if (group.sections) {
|
|
const section = group.sections.find(s => s.id === sectionId);
|
|
if (section) {
|
|
return { section, group, sectionIndex: group.sections.indexOf(section) };
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Find seasonal section using matching algorithm
|
|
*/
|
|
function findSeasonalSectionByMatch(legacySection, seasonGroup, seasonId) {
|
|
if (!seasonGroup || !seasonGroup.sections) return null;
|
|
|
|
const sections = seasonGroup.sections;
|
|
|
|
// Try exact ID match (for descriptive IDs like halloween-tag)
|
|
if (legacySection.id && !legacySection.id.startsWith('seasonal-')) {
|
|
const exactMatch = sections.find(s => {
|
|
const expectedId = `seasonal.${seasonId}.${legacySection.id}`;
|
|
return s.id === expectedId || s.id.endsWith(`.${legacySection.id}`);
|
|
});
|
|
if (exactMatch) {
|
|
LOG(` Matched section by exact ID: ${legacySection.id} → ${exactMatch.id}`);
|
|
return exactMatch;
|
|
}
|
|
}
|
|
|
|
// Try name match
|
|
if (legacySection.name) {
|
|
const nameMatch = sections.find(s => s.name === legacySection.name);
|
|
if (nameMatch) {
|
|
LOG(` Matched section by name: "${legacySection.name}" → ${nameMatch.id}`);
|
|
return nameMatch;
|
|
}
|
|
}
|
|
|
|
// Try type + source pattern match
|
|
if (legacySection.type && legacySection.source !== undefined) {
|
|
const typeSourceMatch = sections.find(s => {
|
|
if (!s.queries || !s.queries[0] || !s.queries[0].queryOptions) return false;
|
|
const queryOptions = s.queries[0].queryOptions;
|
|
|
|
if (legacySection.type === 'Tag' && queryOptions.Tags === legacySection.source) return true;
|
|
if (legacySection.type === 'Genre' && (queryOptions.Genres === legacySection.source || queryOptions.GenreIds === legacySection.source)) return true;
|
|
if (legacySection.type === 'Parent' && queryOptions.ParentId === legacySection.source) return true;
|
|
return false;
|
|
});
|
|
if (typeSourceMatch) {
|
|
LOG(` Matched section by type+source: ${legacySection.type}/${legacySection.source} → ${typeSourceMatch.id}`);
|
|
return typeSourceMatch;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Find discovery section by ID
|
|
*/
|
|
function findDiscoverySectionById(sectionId, groups) {
|
|
for (const group of groups) {
|
|
if (group.sections) {
|
|
const section = group.sections.find(s => s.id === sectionId);
|
|
if (section) {
|
|
return { section, group, sectionIndex: group.sections.indexOf(section) };
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Merge legacy section config into default section
|
|
*/
|
|
function mergeSectionConfig(defaultSection, legacyConfig) {
|
|
const merged = JSON.parse(JSON.stringify(defaultSection));
|
|
|
|
// Map root fields — missing legacy enabled means on (same as seasonal/custom)
|
|
merged.enabled = legacyConfig.enabled !== false;
|
|
if (legacyConfig.name !== undefined) merged.name = legacyConfig.name;
|
|
if (legacyConfig.order !== undefined) merged.order = legacyConfig.order;
|
|
if (legacyConfig.cardFormat !== undefined) merged.cardFormat = legacyConfig.cardFormat;
|
|
if (legacyConfig.startDate !== undefined) merged.startDate = legacyConfig.startDate;
|
|
if (legacyConfig.endDate !== undefined) merged.endDate = legacyConfig.endDate;
|
|
if (legacyConfig.discoveryEnabled !== undefined) merged.discoveryEnabled = legacyConfig.discoveryEnabled;
|
|
|
|
// RenderMode mapping: only use spotlight if renderMode doesn't exist
|
|
if (legacyConfig.renderMode !== undefined) {
|
|
merged.renderMode = legacyConfig.renderMode;
|
|
} else if (legacyConfig.spotlight === true) {
|
|
merged.renderMode = 'Spotlight';
|
|
}
|
|
|
|
if (legacyConfig.spotlightConfig !== undefined) merged.spotlightConfig = legacyConfig.spotlightConfig;
|
|
if (legacyConfig.overflowCard !== undefined) merged.overflowCard = legacyConfig.overflowCard;
|
|
if (legacyConfig.minimumShowsForNetwork !== undefined) merged.minimumShowsForNetwork = legacyConfig.minimumShowsForNetwork;
|
|
if (legacyConfig.flattenSeries !== undefined) merged.flattenSeries = legacyConfig.flattenSeries;
|
|
if (legacyConfig.viewMoreUrl !== undefined) merged.viewMoreUrl = legacyConfig.viewMoreUrl;
|
|
|
|
// Map query options fields
|
|
if (merged.queries && merged.queries.length > 0) {
|
|
const query = merged.queries[0];
|
|
if (!query.queryOptions) query.queryOptions = {};
|
|
|
|
if (legacyConfig.itemLimit !== undefined) query.queryOptions.Limit = legacyConfig.itemLimit;
|
|
if (legacyConfig.sortOrder !== undefined) query.queryOptions.SortBy = legacyConfig.sortOrder;
|
|
if (legacyConfig.sortOrderDirection !== undefined) query.queryOptions.SortOrder = legacyConfig.sortOrderDirection;
|
|
if (legacyConfig.searchTerm !== undefined && legacyConfig.searchTerm) query.queryOptions.SearchTerm = legacyConfig.searchTerm;
|
|
if (legacyConfig.includeItemTypes !== undefined) {
|
|
query.queryOptions.IncludeItemTypes = Array.isArray(legacyConfig.includeItemTypes)
|
|
? legacyConfig.includeItemTypes
|
|
: legacyConfig.includeItemTypes.split(',').map(s => s.trim()).filter(Boolean);
|
|
}
|
|
if (legacyConfig.isPlayed !== undefined && legacyConfig.isPlayed !== null) {
|
|
query.queryOptions.IsUnplayed = legacyConfig.isPlayed === false;
|
|
}
|
|
if (legacyConfig.minAgeInDays !== undefined && legacyConfig.minAgeInDays !== null) {
|
|
query.minAge = legacyConfig.minAgeInDays;
|
|
}
|
|
if (legacyConfig.maxAgeInDays !== undefined && legacyConfig.maxAgeInDays !== null) {
|
|
query.maxAge = legacyConfig.maxAgeInDays;
|
|
}
|
|
|
|
// Merge additionalQueryOptions
|
|
if (legacyConfig.additionalQueryOptions && Array.isArray(legacyConfig.additionalQueryOptions)) {
|
|
legacyConfig.additionalQueryOptions.forEach(opt => {
|
|
if (opt && opt.key && opt.value !== undefined) {
|
|
query.queryOptions[opt.key] = opt.value;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
return merged;
|
|
}
|
|
|
|
/**
|
|
* Convert legacy Type/source to queries array
|
|
*/
|
|
function convertLegacyTypeToQuery(type, source, legacySection) {
|
|
const query = {
|
|
queryOptions: {}
|
|
};
|
|
|
|
// Build base queryOptions from legacy section fields
|
|
if (legacySection.itemLimit !== undefined) query.queryOptions.Limit = legacySection.itemLimit;
|
|
if (legacySection.sortOrder !== undefined) query.queryOptions.SortBy = legacySection.sortOrder;
|
|
if (legacySection.sortOrderDirection !== undefined) query.queryOptions.SortOrder = legacySection.sortOrderDirection;
|
|
if (legacySection.searchTerm !== undefined && legacySection.searchTerm) query.queryOptions.SearchTerm = legacySection.searchTerm;
|
|
if (legacySection.includeItemTypes !== undefined) {
|
|
query.queryOptions.IncludeItemTypes = Array.isArray(legacySection.includeItemTypes)
|
|
? legacySection.includeItemTypes
|
|
: legacySection.includeItemTypes.split(',').map(s => s.trim()).filter(Boolean);
|
|
}
|
|
if (legacySection.isPlayed !== undefined && legacySection.isPlayed !== null) {
|
|
query.queryOptions.IsUnplayed = legacySection.isPlayed === false;
|
|
}
|
|
if (legacySection.minAgeInDays !== undefined && legacySection.minAgeInDays !== null) {
|
|
query.minAge = legacySection.minAgeInDays;
|
|
}
|
|
if (legacySection.maxAgeInDays !== undefined && legacySection.maxAgeInDays !== null) {
|
|
query.maxAge = legacySection.maxAgeInDays;
|
|
}
|
|
|
|
// Convert Type/source to queryOptions
|
|
if (type === 'Playlist' || type === 'Collection' || type === 'Parent') {
|
|
if (source && source.trim()) {
|
|
query.queryOptions.ParentId = source.trim();
|
|
}
|
|
} else if (type === 'Genre') {
|
|
const genreSource = source ? source.trim() : '';
|
|
if (genreSource) {
|
|
// Check if it's a 32-char hex ID
|
|
if (/^[a-f0-9]{32}$/i.test(genreSource)) {
|
|
query.queryOptions.GenreIds = genreSource;
|
|
} else {
|
|
// Comma-separated genre names
|
|
query.queryOptions.Genres = genreSource;
|
|
}
|
|
}
|
|
} else if (type === 'Tag') {
|
|
if (source && source.trim()) {
|
|
query.queryOptions.Tags = source.trim();
|
|
}
|
|
}
|
|
|
|
// Merge additionalQueryOptions
|
|
if (legacySection.additionalQueryOptions && Array.isArray(legacySection.additionalQueryOptions)) {
|
|
legacySection.additionalQueryOptions.forEach(opt => {
|
|
if (opt && opt.key && opt.value !== undefined) {
|
|
query.queryOptions[opt.key] = opt.value;
|
|
}
|
|
});
|
|
}
|
|
|
|
return query;
|
|
}
|
|
|
|
/**
|
|
* Migrate HOME sections
|
|
*/
|
|
function migrateHomeSections(legacyHomeScreen, newConfig) {
|
|
LOG('Migrating HOME sections...');
|
|
|
|
// Migrate recentlyReleased.movies
|
|
if (legacyHomeScreen.recentlyReleased?.movies) {
|
|
const found = findSectionInDefaults('recentlyReleased.movies', newConfig.HOME_SECTION_GROUPS);
|
|
if (found) {
|
|
const moviesConfig = legacyHomeScreen.recentlyReleased.movies;
|
|
const parentEnabled = legacyHomeScreen.recentlyReleased.enabled !== false;
|
|
const merged = mergeSectionConfig(found.section, {
|
|
...moviesConfig,
|
|
enabled: moviesConfig.enabled !== undefined ? moviesConfig.enabled : parentEnabled
|
|
});
|
|
Object.assign(found.section, merged);
|
|
LOG(' Migrated recentlyReleased.movies');
|
|
}
|
|
}
|
|
|
|
// Migrate recentlyReleased.episodes
|
|
if (legacyHomeScreen.recentlyReleased?.episodes) {
|
|
const found = findSectionInDefaults('recentlyReleased.episodes', newConfig.HOME_SECTION_GROUPS);
|
|
if (found) {
|
|
const episodesConfig = legacyHomeScreen.recentlyReleased.episodes;
|
|
const parentEnabled = legacyHomeScreen.recentlyReleased.enabled !== false;
|
|
const merged = mergeSectionConfig(found.section, {
|
|
...episodesConfig,
|
|
enabled: episodesConfig.enabled !== undefined ? episodesConfig.enabled : parentEnabled
|
|
});
|
|
Object.assign(found.section, merged);
|
|
LOG(' Migrated recentlyReleased.episodes');
|
|
}
|
|
}
|
|
|
|
// Migrate recentlyAddedInLibrary
|
|
if (legacyHomeScreen.recentlyAddedInLibrary && typeof legacyHomeScreen.recentlyAddedInLibrary === 'object') {
|
|
const libraryConfigs = legacyHomeScreen.recentlyAddedInLibrary;
|
|
let recentlyAddedGroup = newConfig.HOME_SECTION_GROUPS.find(g => g.name === 'Recently Added' || g.id === 'home-recently-added');
|
|
|
|
if (!recentlyAddedGroup) {
|
|
recentlyAddedGroup = {
|
|
id: 'home-recently-added',
|
|
name: 'Recently Added',
|
|
sections: []
|
|
};
|
|
newConfig.HOME_SECTION_GROUPS.push(recentlyAddedGroup);
|
|
}
|
|
|
|
Object.entries(libraryConfigs).forEach(([libraryId, libConfig]) => {
|
|
const sectionId = `recently-added-${libraryId}`;
|
|
let includeItemTypes = ['Movie', 'Episode'];
|
|
|
|
if (libConfig.includeItemTypes) {
|
|
includeItemTypes = Array.isArray(libConfig.includeItemTypes)
|
|
? libConfig.includeItemTypes
|
|
: libConfig.includeItemTypes.split(',').map(s => s.trim()).filter(Boolean);
|
|
}
|
|
|
|
const newSection = {
|
|
id: sectionId,
|
|
name: libConfig.name || `Recently Added Media`,
|
|
enabled: libConfig.enabled !== false,
|
|
order: libConfig.order || 61,
|
|
cardFormat: libConfig.cardFormat || 'Poster',
|
|
jellyfinId: 'latestmedia',
|
|
ttl: 0,
|
|
queries: [{
|
|
path: '/Items/Latest',
|
|
queryOptions: {
|
|
ParentId: libraryId,
|
|
IncludeItemTypes: includeItemTypes,
|
|
SortBy: 'DateCreated',
|
|
SortOrder: 'Descending',
|
|
Limit: libConfig.itemLimit || 16
|
|
}
|
|
}]
|
|
};
|
|
|
|
recentlyAddedGroup.sections.push(newSection);
|
|
LOG(` Migrated recentlyAddedInLibrary[${libraryId}]`);
|
|
});
|
|
}
|
|
|
|
// Migrate other home sections
|
|
const sectionMappings = {
|
|
watchAgain: 'watchAgain',
|
|
watchlist: 'watchlist',
|
|
upcoming: 'upcoming',
|
|
imdbTop250: 'imdbTop250',
|
|
trending: 'trending',
|
|
popularTVNetworks: 'popularTVNetworks'
|
|
};
|
|
|
|
Object.entries(sectionMappings).forEach(([legacyKey, sectionId]) => {
|
|
if (legacyHomeScreen[legacyKey]) {
|
|
const found = findSectionInDefaults(sectionId, newConfig.HOME_SECTION_GROUPS);
|
|
if (found) {
|
|
const merged = mergeSectionConfig(found.section, legacyHomeScreen[legacyKey]);
|
|
Object.assign(found.section, merged);
|
|
LOG(` Migrated ${legacyKey}`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Migrate SEASONAL sections
|
|
*/
|
|
function migrateSeasonalSections(legacyHomeScreen, newConfig) {
|
|
if (!legacyHomeScreen.seasonal?.seasons || !Array.isArray(legacyHomeScreen.seasonal.seasons)) {
|
|
return;
|
|
}
|
|
|
|
LOG('Migrating SEASONAL sections...');
|
|
|
|
legacyHomeScreen.seasonal.seasons.forEach(legacySeason => {
|
|
const seasonId = legacySeason.id || legacySeason.name?.toLowerCase().replace(/\s+/g, '-');
|
|
if (!seasonId) {
|
|
WARN(' Skipping season without id or name');
|
|
return;
|
|
}
|
|
|
|
// Find matching seasonal group
|
|
const seasonalGroup = newConfig.SEASONAL_SECTION_GROUPS.find(g => {
|
|
const groupId = g.id || g.name?.toLowerCase().replace(/\s+/g, '-');
|
|
return groupId === `seasonal-${seasonId}` || groupId === seasonId;
|
|
});
|
|
|
|
if (!seasonalGroup) {
|
|
WARN(` No matching seasonal group found for season: ${seasonId}`);
|
|
return;
|
|
}
|
|
|
|
LOG(` Processing season: ${seasonId}`);
|
|
|
|
if (legacySeason.sections && Array.isArray(legacySeason.sections)) {
|
|
legacySeason.sections.forEach(legacySection => {
|
|
// Try to find matching section
|
|
const matchedSection = findSeasonalSectionByMatch(legacySection, seasonalGroup, seasonId);
|
|
|
|
if (matchedSection) {
|
|
// Merge legacy config into matched section
|
|
const merged = mergeSectionConfig(matchedSection, {
|
|
...legacySection,
|
|
startDate: legacySeason.startDate || matchedSection.startDate,
|
|
endDate: legacySeason.endDate || matchedSection.endDate
|
|
});
|
|
Object.assign(matchedSection, merged);
|
|
LOG(` Migrated section: ${legacySection.id || legacySection.name} → ${matchedSection.id}`);
|
|
} else {
|
|
LOG(`Converting generic section to custom: ${legacySection.id}`);
|
|
convertSeasonalSectionToCustom(legacySection, legacySeason, newConfig);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Convert seasonal section with generic ID to custom section
|
|
*/
|
|
function convertSeasonalSectionToCustom(legacySection, legacySeason, newConfig) {
|
|
const query = convertLegacyTypeToQuery(legacySection.type, legacySection.source, legacySection);
|
|
|
|
const customSection = {
|
|
id: `custom-${Date.now()}-${Math.random()}`,
|
|
name: legacySection.name || 'Custom Section',
|
|
enabled: legacySection.enabled !== false,
|
|
order: legacySection.order,
|
|
cardFormat: legacySection.cardFormat || 'Poster',
|
|
queries: [query]
|
|
};
|
|
|
|
// Map _parentItemType based on type
|
|
if (legacySection.type === 'Playlist') {
|
|
customSection._parentItemType = 'Playlist';
|
|
} else if (legacySection.type === 'Collection') {
|
|
customSection._parentItemType = 'Collection';
|
|
} else if (legacySection.type === 'Parent') {
|
|
customSection._parentItemType = 'Generic Parent';
|
|
}
|
|
// Tag and Genre don't need _parentItemType
|
|
|
|
// Map additional fields
|
|
if (legacySection.renderMode !== undefined) {
|
|
customSection.renderMode = legacySection.renderMode;
|
|
} else if (legacySection.spotlight === true) {
|
|
customSection.renderMode = 'Spotlight';
|
|
}
|
|
if (legacySection.discoveryEnabled !== undefined) customSection.discoveryEnabled = legacySection.discoveryEnabled;
|
|
if (legacySection.spotlightConfig !== undefined) customSection.spotlightConfig = legacySection.spotlightConfig;
|
|
if (legacySection.overflowCard !== undefined) customSection.overflowCard = legacySection.overflowCard;
|
|
|
|
const startDate = legacySection.startDate || legacySeason.startDate;
|
|
const endDate = legacySection.endDate || legacySeason.endDate;
|
|
if (startDate !== undefined) customSection.startDate = startDate;
|
|
if (endDate !== undefined) customSection.endDate = endDate;
|
|
|
|
// Add to custom sections group
|
|
let customGroup = newConfig.CUSTOM_SECTION_GROUPS.find(g => g.name === 'Custom Sections' || g.id === 'custom-sections');
|
|
if (!customGroup) {
|
|
customGroup = {
|
|
id: 'custom-sections',
|
|
name: 'Custom Sections',
|
|
sections: []
|
|
};
|
|
newConfig.CUSTOM_SECTION_GROUPS.push(customGroup);
|
|
}
|
|
|
|
customGroup.sections.push(customSection);
|
|
LOG(` Added as custom section: ${customSection.id}`);
|
|
}
|
|
|
|
const RECENTLY_WATCHED_PERSON_IDS = new Set([
|
|
'starringActorRecentlyWatched',
|
|
'directedByDirectorRecentlyWatched',
|
|
'writtenByWriterRecentlyWatched'
|
|
]);
|
|
|
|
const RECENTLY_WATCHED_CAPTION_DEFAULT = 'Because you recently watched {Title}';
|
|
|
|
const RECENTLY_WATCHED_NAME_DEFAULTS = {
|
|
starringActorRecentlyWatched: 'Starring {Person}',
|
|
directedByDirectorRecentlyWatched: 'Directed by {Person}',
|
|
writtenByWriterRecentlyWatched: 'Written by {Person}'
|
|
};
|
|
|
|
function normalizeDiscoveryTemplatePlaceholders(text) {
|
|
if (!text || typeof text !== 'string') return text;
|
|
return text
|
|
.replace(/\[Actor\]/gi, '{Person}')
|
|
.replace(/\[Director\]/gi, '{Person}')
|
|
.replace(/\[Writer\]/gi, '{Person}')
|
|
.replace(/\[Person\]/gi, '{Person}')
|
|
.replace(/\[Movie\]/gi, '{Title}')
|
|
.replace(/\[Title\]/gi, '{Title}');
|
|
}
|
|
|
|
/**
|
|
* Split legacy combined "Starring X because you recently watched Y" into name + caption.
|
|
*/
|
|
function splitLegacyRecentlyWatchedName(legacyName) {
|
|
if (!legacyName || typeof legacyName !== 'string') return null;
|
|
const match = legacyName.match(/^(.*?)\s+because you recently watched\s+(.*)$/i);
|
|
if (!match) return null;
|
|
let name = normalizeDiscoveryTemplatePlaceholders(match[1].trim());
|
|
let captionRight = normalizeDiscoveryTemplatePlaceholders(match[2].trim());
|
|
const caption = captionRight
|
|
? `Because you recently watched ${captionRight}`
|
|
: RECENTLY_WATCHED_CAPTION_DEFAULT;
|
|
// Prefer canonical caption placeholder when right side is Movie/Title token only
|
|
if (/^\{Title\}$/i.test(captionRight) || /^\[Movie\]$/i.test(match[2].trim())) {
|
|
return { name, caption: RECENTLY_WATCHED_CAPTION_DEFAULT };
|
|
}
|
|
return { name, caption };
|
|
}
|
|
|
|
/**
|
|
* Migrate DISCOVERY sections
|
|
*/
|
|
function migrateDiscoverySections(legacyHomeScreen, newConfig) {
|
|
if (!legacyHomeScreen.discovery?.sectionTypes) {
|
|
return;
|
|
}
|
|
|
|
LOG('Migrating DISCOVERY sections...');
|
|
|
|
const discoverySectionTypes = legacyHomeScreen.discovery.sectionTypes;
|
|
Object.entries(discoverySectionTypes).forEach(([sectionKey, legacySectionConfig]) => {
|
|
const found = findDiscoverySectionById(sectionKey, newConfig.DISCOVERY_SECTION_GROUPS);
|
|
|
|
if (found) {
|
|
found.section.enabled = legacySectionConfig.enabled !== false;
|
|
if (legacySectionConfig.itemLimit !== undefined) found.section.itemLimit = legacySectionConfig.itemLimit;
|
|
if (legacySectionConfig.cardFormat !== undefined) found.section.cardFormat = legacySectionConfig.cardFormat;
|
|
if (legacySectionConfig.isPlayed !== undefined && legacySectionConfig.isPlayed !== null) {
|
|
found.section.isPlayed = legacySectionConfig.isPlayed;
|
|
}
|
|
if (legacySectionConfig.minimumItems !== undefined) found.section.minimumItems = legacySectionConfig.minimumItems;
|
|
if (legacySectionConfig.sortOrder !== undefined) found.section.sortOrder = legacySectionConfig.sortOrder;
|
|
if (legacySectionConfig.sortOrderDirection !== undefined) found.section.sortOrderDirection = legacySectionConfig.sortOrderDirection;
|
|
|
|
const isRecentlyWatchedPerson = RECENTLY_WATCHED_PERSON_IDS.has(sectionKey);
|
|
if (legacySectionConfig.name !== undefined) {
|
|
const split = splitLegacyRecentlyWatchedName(legacySectionConfig.name);
|
|
if (split) {
|
|
found.section.name = split.name;
|
|
found.section.caption = split.caption;
|
|
} else if (isRecentlyWatchedPerson) {
|
|
found.section.name = RECENTLY_WATCHED_NAME_DEFAULTS[sectionKey]
|
|
|| normalizeDiscoveryTemplatePlaceholders(legacySectionConfig.name);
|
|
if (!found.section.caption) {
|
|
found.section.caption = RECENTLY_WATCHED_CAPTION_DEFAULT;
|
|
}
|
|
} else {
|
|
found.section.name = legacySectionConfig.name;
|
|
}
|
|
} else if (isRecentlyWatchedPerson && !found.section.caption) {
|
|
found.section.caption = RECENTLY_WATCHED_CAPTION_DEFAULT;
|
|
}
|
|
|
|
if (legacySectionConfig.caption !== undefined) {
|
|
found.section.caption = normalizeDiscoveryTemplatePlaceholders(legacySectionConfig.caption);
|
|
}
|
|
|
|
LOG(` Migrated discovery section: ${sectionKey}`);
|
|
} else {
|
|
WARN(` Discovery section not found in defaults: ${sectionKey}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Migrate CUSTOM sections
|
|
*/
|
|
function migrateCustomSections(legacyHomeScreen, newConfig) {
|
|
if (!legacyHomeScreen.customSections || !Array.isArray(legacyHomeScreen.customSections)) {
|
|
return;
|
|
}
|
|
|
|
LOG('Migrating CUSTOM sections...');
|
|
|
|
let customGroup = newConfig.CUSTOM_SECTION_GROUPS.find(g => g.name === 'Custom Sections' || g.id === 'custom-sections');
|
|
if (!customGroup) {
|
|
customGroup = {
|
|
id: 'custom-sections',
|
|
name: 'Custom Sections',
|
|
sections: []
|
|
};
|
|
newConfig.CUSTOM_SECTION_GROUPS.push(customGroup);
|
|
}
|
|
|
|
legacyHomeScreen.customSections.forEach(legacySection => {
|
|
const query = convertLegacyTypeToQuery(legacySection.type, legacySection.source, legacySection);
|
|
|
|
const customSection = {
|
|
id: `custom-${Date.now()}-${Math.random()}`,
|
|
name: legacySection.name || 'Custom Section',
|
|
enabled: legacySection.enabled !== false,
|
|
order: legacySection.order,
|
|
cardFormat: legacySection.cardFormat || 'Poster',
|
|
queries: [query]
|
|
};
|
|
|
|
// Map _parentItemType based on type
|
|
if (legacySection.type === 'Playlist') {
|
|
customSection._parentItemType = 'Playlist';
|
|
} else if (legacySection.type === 'Collection') {
|
|
customSection._parentItemType = 'Collection';
|
|
} else if (legacySection.type === 'Parent') {
|
|
customSection._parentItemType = 'Generic Parent';
|
|
}
|
|
// Tag and Genre don't need _parentItemType
|
|
|
|
// Map additional fields
|
|
if (legacySection.renderMode !== undefined) {
|
|
customSection.renderMode = legacySection.renderMode;
|
|
} else if (legacySection.spotlight === true) {
|
|
customSection.renderMode = 'Spotlight';
|
|
}
|
|
if (legacySection.discoveryEnabled !== undefined) customSection.discoveryEnabled = legacySection.discoveryEnabled;
|
|
if (legacySection.spotlightConfig !== undefined) customSection.spotlightConfig = legacySection.spotlightConfig;
|
|
if (legacySection.overflowCard !== undefined) customSection.overflowCard = legacySection.overflowCard;
|
|
|
|
customGroup.sections.push(customSection);
|
|
LOG(` Migrated custom section: ${customSection.id}`);
|
|
});
|
|
}
|
|
|
|
// Expose migration function
|
|
window.migrateHomeScreenConfig = migrateHomeScreenConfig;
|
|
|
|
LOG('HomeScreen migration module loaded');
|
|
|
|
})();
|