mirror of
https://github.com/ranaldsgift/KefinTweaks.git
synced 2026-11-05 15:52:47 +01:00
212 lines
8.0 KiB
JavaScript
212 lines
8.0 KiB
JavaScript
// KefinTweaks Flatten Single Season Shows
|
|
// Flattens series with only 1 season to show episodes directly on the series details page
|
|
// Requires: cardBuilder.js, utils.js modules to be loaded before this script
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
const LOG = (...args) => console.log('[KefinTweaks FlattenSingleSeasonShows]', ...args);
|
|
const WARN = (...args) => console.warn('[KefinTweaks FlattenSingleSeasonShows]', ...args);
|
|
const ERR = (...args) => console.error('[KefinTweaks FlattenSingleSeasonShows]', ...args);
|
|
|
|
/**
|
|
* Fetches all episodes for a given series
|
|
* @param {string} seriesId - The series ID
|
|
* @returns {Promise<Array>} - Array of episode items
|
|
*/
|
|
async function fetchEpisodesForSeries(seriesId) {
|
|
try {
|
|
const apiClient = window.ApiClient;
|
|
const userId = apiClient.getCurrentUserId();
|
|
const serverUrl = apiClient.serverAddress();
|
|
const token = apiClient.accessToken();
|
|
|
|
LOG(`Fetching episodes for series: ${seriesId}`);
|
|
const episodesUrl = `${serverUrl}/Shows/${seriesId}/Episodes?UserId=${userId}&Fields=UserData&EnableImageTypes=Primary`;
|
|
const episodesRes = await fetch(episodesUrl, {
|
|
headers: { "Authorization": `MediaBrowser Token="${token}"` }
|
|
});
|
|
|
|
if (!episodesRes.ok) {
|
|
throw new Error(`HTTP ${episodesRes.status}: ${episodesRes.statusText}`);
|
|
}
|
|
|
|
const episodesData = await episodesRes.json();
|
|
const episodes = episodesData.Items || [];
|
|
|
|
LOG(`Fetched ${episodes.length} episodes for series: ${seriesId}`);
|
|
return episodes;
|
|
} catch (error) {
|
|
ERR(`Failed to fetch episodes for series ${seriesId}:`, error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Flattens a single-season show by creating a "Season 1" section with all episodes
|
|
* @param {string} seriesId - The series ID
|
|
*/
|
|
async function flattenShow(seriesId) {
|
|
LOG(`Flattening show: ${seriesId}`);
|
|
|
|
// Check if already flattened for this series (avoid duplicates)
|
|
const activePage = document.querySelector('.libraryPage:not(.hide)');
|
|
if (activePage && activePage.dataset.flattenedSeriesId === seriesId) {
|
|
LOG(`Show ${seriesId} already flattened, skipping`);
|
|
return;
|
|
}
|
|
|
|
// Check if flattened section already exists
|
|
const existingSection = activePage?.querySelector('.flattened-season-section');
|
|
if (existingSection) {
|
|
LOG(`Flattened section already exists, skipping`);
|
|
return;
|
|
}
|
|
|
|
// Get all episodes for the series
|
|
const episodes = await fetchEpisodesForSeries(seriesId);
|
|
if (episodes.length === 0) {
|
|
WARN(`No episodes found for series: ${seriesId}`);
|
|
return;
|
|
}
|
|
|
|
// Check for cardBuilder
|
|
if (!window.cardBuilder || !window.cardBuilder.renderCards) {
|
|
WARN('cardBuilder.renderCards not available');
|
|
return;
|
|
}
|
|
|
|
// Find the detailPageContent
|
|
const detailPageContent = activePage?.querySelector('.detailPageContent');
|
|
if (!detailPageContent) {
|
|
WARN('detailPageContent not found');
|
|
return;
|
|
}
|
|
|
|
// Find the #childrenCollapsible div
|
|
const childrenCollapsible = detailPageContent.querySelector('#childrenCollapsible');
|
|
if (!childrenCollapsible) {
|
|
WARN('#childrenCollapsible not found');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Get the season ID from the first episode's ParentId
|
|
const seasonId = episodes[0]?.SeasonId;
|
|
let viewMoreUrl = null;
|
|
|
|
if (seasonId) {
|
|
const apiClient = window.ApiClient;
|
|
const serverId = apiClient.serverId();
|
|
viewMoreUrl = `${apiClient._serverAddress || apiClient.serverAddress()}/web/#/details?id=${seasonId}&serverId=${serverId}`;
|
|
LOG(`Setting viewMoreUrl to season details: ${viewMoreUrl}`);
|
|
} else {
|
|
WARN('Could not determine season ID from episodes');
|
|
}
|
|
|
|
// Render Season 1 section using renderCards
|
|
const seasonSection = window.cardBuilder.renderCards(
|
|
episodes,
|
|
'Season 1',
|
|
viewMoreUrl,
|
|
false, // overflowCard
|
|
null // cardFormat (use default)
|
|
);
|
|
|
|
// Mark as flattened section
|
|
seasonSection.classList.add('flattened-season-section');
|
|
seasonSection.setAttribute('data-flattened-section', 'true');
|
|
|
|
const scrollerContainer = seasonSection.querySelector('.emby-scroller');
|
|
if (scrollerContainer) {
|
|
scrollerContainer.classList.add('no-padding');
|
|
}
|
|
|
|
const sectionTitleContainer = seasonSection.querySelector('.sectionTitleContainer');
|
|
if (sectionTitleContainer) {
|
|
sectionTitleContainer.classList.add('no-padding');
|
|
}
|
|
|
|
// Prepend the Season 1 section right before #childrenCollapsible
|
|
childrenCollapsible.parentNode.insertBefore(seasonSection, childrenCollapsible);
|
|
|
|
// Mark as flattened for this series to avoid duplicates
|
|
if (activePage) {
|
|
activePage.dataset.flattenedSeriesId = seriesId;
|
|
}
|
|
|
|
LOG(`Successfully flattened show ${seriesId} with ${episodes.length} episodes`);
|
|
} catch (error) {
|
|
ERR('Error rendering flattened season section:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the flatten single season shows hook
|
|
*/
|
|
function initializeFlattenHook() {
|
|
if (!window.KefinTweaksUtils || !window.KefinTweaksUtils.onViewPage) {
|
|
WARN('KefinTweaksUtils.onViewPage not available, retrying in 1 second');
|
|
setTimeout(initializeFlattenHook, 1000);
|
|
return;
|
|
}
|
|
|
|
LOG('Registering flatten single season shows handler with KefinTweaksUtils');
|
|
|
|
window.KefinTweaksUtils.onViewPage(
|
|
async (view, element, itemPromise) => {
|
|
// Only handle details pages
|
|
const activePage = document.querySelector('.libraryPage:not(.hide)');
|
|
if (!activePage) return;
|
|
|
|
// Remove any existing flattened section and reset flag when page changes
|
|
const existingSection = activePage.querySelector('.flattened-season-section');
|
|
if (existingSection) {
|
|
existingSection.remove();
|
|
}
|
|
delete activePage.dataset.flattenedSeriesId;
|
|
|
|
// Await the item promise to get the actual item data
|
|
const item = await itemPromise;
|
|
|
|
// Check if item is a Series and has only 1 ChildCount
|
|
if (item && item.Type === 'Series' && item.ChildCount === 1) {
|
|
LOG(`Found single-season series: ${item.Id} (${item.Name})`);
|
|
|
|
// Small delay to ensure details DOM is ready
|
|
setTimeout(async () => {
|
|
await flattenShow(item.Id);
|
|
}, 100);
|
|
}
|
|
},
|
|
{
|
|
immediate: true,
|
|
pages: ['details']
|
|
}
|
|
);
|
|
|
|
LOG('Flatten single season shows hook initialized');
|
|
}
|
|
|
|
// Initialize the hook when the script loads
|
|
function initialize() {
|
|
if (!window.KefinTweaksUtils || !window.KefinTweaksUtils.onViewPage) {
|
|
WARN('KefinTweaksUtils not available, retrying in 1 second');
|
|
setTimeout(initialize, 1000);
|
|
return;
|
|
}
|
|
|
|
initializeFlattenHook();
|
|
}
|
|
|
|
// Start initialization
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initialize);
|
|
} else {
|
|
initialize();
|
|
}
|
|
|
|
LOG('Script loaded successfully');
|
|
})();
|
|
|