From 0a5f0f41b6409aa15131634dafa66e3749e5667f Mon Sep 17 00:00:00 2001 From: brys0 <35676267+brys0@users.noreply.github.com> Date: Tue, 11 Feb 2025 12:35:22 -0600 Subject: [PATCH 1/8] lint: Add basic jsdoc for credential grabbing --- slideshowpure.js | 94 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 13 deletions(-) diff --git a/slideshowpure.js b/slideshowpure.js index 8f98520..f9c6bfe 100644 --- a/slideshowpure.js +++ b/slideshowpure.js @@ -1,23 +1,57 @@ + +/** + * Credentials for a given server + * @typedef {Object} ServerCredentials + * @property {string} Id + * @property {string} AccessToken + */ + +/** + * Credentials + * @typedef {Object} Credentials + * @property {ServerCredentials[]} Servers + */ + +/** + * Fetches the credentials inside of localstorage + * @returns {{token: string, userId: string}} Credential resource + */ +const getJellyfinCredentials = () => { + const jellyfinCreds = localStorage.getItem('jellyfin_credentials'); + + try { + /** + * @type {Credentials} + */ + const serverCredentials = JSON.parse(jellyfinCreds); + + const firstServer = serverCredentials.Servers[0]; + + if (!firstServer) { + console.error('Could not find credentials for the client'); + return + } + + return firstServer.AccessToken; + } catch (e) { + console.error('Could not parse jellyfin credentials', e); + } +} + const slidesInit = () => { const shuffleInterval = 8000; let isTransitioning = false; const listFileName = `${window.location.origin}/web/avatars/list.txt`; - const jsonCredentials = sessionStorage.getItem("json-credentials"); - let userId = null; - let token = null; - const deviceId = localStorage.getItem("_deviceId2"); - console.log(deviceId); - if (jsonCredentials) { - const credentials = JSON.parse(jsonCredentials); - userId = credentials.Servers[0].UserId; - token = credentials.Servers[0].AccessToken; - } + + const { token, userId } = getJellyfinCredentials(); + const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); const truncateText = (element, maxLength) => { let text = element.innerText; if (text.length > maxLength) element.innerText = text.substring(0, maxLength) + "..."; }; + const createSlideElement = (item, title) => { const itemId = item.Id; const plot = item.Overview || "No overview available"; @@ -46,33 +80,43 @@ const slidesInit = () => { backdrop.className = "backdrop"; backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; backdrop.alt = "Backdrop"; + const backdropOverlay = document.createElement("div"); backdropOverlay.className = "backdrop-overlay"; + const backdropContainer = document.createElement("div"); backdropContainer.className = "backdrop-container"; backdropContainer.appendChild(backdrop); backdropContainer.appendChild(backdropOverlay); + const logo = document.createElement("img"); logo.className = "logo"; logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; logo.alt = "Logo"; + const logoContainer = document.createElement("div"); logoContainer.className = "logo-container"; logoContainer.appendChild(logo); + const featuredContent = document.createElement("div"); featuredContent.className = "featured-content"; featuredContent.textContent = title; + const plotElement = document.createElement("div"); plotElement.className = "plot"; plotElement.textContent = plot; truncateText(plotElement, 360); + const plotContainer = document.createElement("div"); plotContainer.className = "plot-container"; plotContainer.appendChild(plotElement); + const gradientOverlay = document.createElement("div"); gradientOverlay.className = "gradient-overlay"; + const runTimeElement = document.createElement("div"); runTimeElement.className = "runTime"; + if (season === undefined) { const milliseconds = runtime / 10000; const currentTime = new Date(); @@ -83,8 +127,10 @@ const slidesInit = () => { } else { runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; } + const ratingTest = document.createElement("div"); ratingTest.className = "rating-value"; + const imdbLogo = document.createElement("img"); imdbLogo.className = "imdb-logo"; imdbLogo.src = @@ -93,6 +139,7 @@ const slidesInit = () => { imdbLogo.style.width = "30px"; imdbLogo.style.height = "30px"; ratingTest.appendChild(imdbLogo); + if (typeof rating === "number") { const formattedRating = rating.toFixed(1); ratingTest.innerHTML += `${formattedRating} ⭐`; @@ -101,21 +148,25 @@ const slidesInit = () => { ratingTest.innerHTML += `N/A ⭐`; } ratingTest.appendChild(createSeparator()); + const tomatoRatingDiv = document.createElement("div"); tomatoRatingDiv.className = "tomato-rating"; - const criticRating = item.CriticRating; + const tomatoLogo = document.createElement("img"); tomatoLogo.className = "tomato-logo"; + tomatoLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; + const criticLogo = document.createElement("img"); criticLogo.className = "critic-logo"; - tomatoLogo.src = - "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; + let valueElement; if (typeof criticRating === "number") { valueElement = document.createTextNode(`${criticRating}% `); } else { valueElement = document.createTextNode(`N/A `); } + if (criticRating === undefined || criticRating <= 59) { criticLogo.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; @@ -128,22 +179,28 @@ const slidesInit = () => { tomatoLogo.style.height = "17px"; criticLogo.style.width = "15px"; criticLogo.style.height = "15px"; + tomatoRatingDiv.appendChild(tomatoLogo); tomatoRatingDiv.appendChild(valueElement); tomatoRatingDiv.appendChild(criticLogo); tomatoRatingDiv.appendChild(createSeparator()); + const ageRatingDiv = document.createElement("div"); ageRatingDiv.className = "age-rating"; + if (item.OfficialRating) { ageRatingDiv.innerHTML = `${item.OfficialRating}`; } else { ageRatingDiv.innerHTML = `N/A`; } + const calender = "📅"; const premiereDate = document.createElement("div"); premiereDate.className = "date"; + const year = date ? new Date(date).getFullYear() : "N/A"; premiereDate.textContent = isNaN(year) ? "N/A" : year; + ratingTest.appendChild(tomatoRatingDiv); ratingTest.appendChild(document.createTextNode(calender)); ratingTest.appendChild(premiereDate); @@ -151,6 +208,7 @@ const slidesInit = () => { ratingTest.appendChild(ageRatingDiv); ratingTest.appendChild(createSeparator()); ratingTest.appendChild(runTimeElement); + const genresArray = item.Genres; function parseGenres(genresArray) { if (genresArray && genresArray.length > 0) { @@ -159,29 +217,36 @@ const slidesInit = () => { return "No Genre Available"; } } + const genreElement = document.createElement("div"); genreElement.className = "genre"; genreElement.innerHTML = parseGenres(genresArray); + const infoContainer = document.createElement("div"); infoContainer.className = "info-container"; infoContainer.appendChild(ratingTest); + const playButton = document.createElement("button"); playButton.className = "play-button"; playButton.innerHTML = ` Play Now `; + playButton.onclick = async () => { if (!window.ApiClient) { console.error("Jellyfin API client is not available."); return; } + const apiClient = window.ApiClient; const userId = apiClient.getCurrentUserId(); + if (!userId) { console.error("User not found."); return; } + try { const sessions = await apiClient.getJSON( apiClient.serverAddress() + "/Sessions" @@ -209,15 +274,18 @@ const slidesInit = () => { console.error("Error fetching sessions:", error); } }; + const detailButton = document.createElement("button"); detailButton.className = "detail-button"; detailButton.innerHTML = ` More Details `; + detailButton.onclick = () => { window.top.location.href = `/#!/details?id=${itemId}`; }; + slide.append( logoContainer, backdropContainer, From 69b5b3416cf2c3c8a5778c2d642c6f864e863b80 Mon Sep 17 00:00:00 2001 From: brys0 <35676267+brys0@users.noreply.github.com> Date: Tue, 11 Feb 2025 12:39:35 -0600 Subject: [PATCH 2/8] fix duplication of genres and criticRating --- slideshowpure.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/slideshowpure.js b/slideshowpure.js index 4bde05f..2028767 100644 --- a/slideshowpure.js +++ b/slideshowpure.js @@ -56,13 +56,14 @@ const slidesInit = () => { const itemId = item.Id; const plot = item.Overview || "No overview available"; const rating = item.CommunityRating; - const tomato = item.CriticRating; + const criticRating = item.CriticRating; const runtime = item.RunTimeTicks; - const genre = item.Genres; + const genresArray = item.Genres; const youtube = item.RemoteTrailers; const age = item.OfficialRating; const date = item.PremiereDate; const season = item.ChildCount; + function createSeparator() { const separatorHtml = ''; @@ -209,7 +210,6 @@ const slidesInit = () => { ratingTest.appendChild(createSeparator()); ratingTest.appendChild(runTimeElement); - const genresArray = item.Genres; function parseGenres(genresArray) { if (genresArray && genresArray.length > 0) { return genresArray.slice(0, 3).join(" 🔹 "); From d7ba06356af592d29af7e83f583fb78b8c397e7e Mon Sep 17 00:00:00 2001 From: brys0 <35676267+brys0@users.noreply.github.com> Date: Tue, 11 Feb 2025 12:41:20 -0600 Subject: [PATCH 3/8] return token and userid inside object in jellyfinCredentials --- slideshowpure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slideshowpure.js b/slideshowpure.js index 2028767..0b6e207 100644 --- a/slideshowpure.js +++ b/slideshowpure.js @@ -32,7 +32,7 @@ const getJellyfinCredentials = () => { return } - return firstServer.AccessToken; + return { token: firstServer.AccessToken, userId: firstServer.AccessToken }; } catch (e) { console.error('Could not parse jellyfin credentials', e); } From 50fae42dbf34f18171a016278877935d322a6346 Mon Sep 17 00:00:00 2001 From: M0RPH3US Date: Wed, 12 Feb 2025 01:58:02 +0530 Subject: [PATCH 4/8] Fix Typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d8ef209..7baaf49 100644 --- a/README.md +++ b/README.md @@ -121,8 +121,8 @@ Here I present my version of the same with some code improvements, loading optim main.jellyfin.bundle.js 1. Similarly, search for `main.jellyfin.bundle.js` in the `jellyfin-web` directory. Open it with any code editor with administrator privileges. -2. Search for `this.playbackManager = e;` -3. Right after the `;`, paste the code block `window.PlaybackManager = this.playbackManager;console.log("PlaybackManager is now globally available:", window.PlaybackManager);` +2. Search for `this.playbackManager = e,` +3. Right after the `,`, paste the code block `window.PlaybackManager = this.playbackManager;console.log("PlaybackManager is now globally available:", window.PlaybackManager);` From b47051831445404af0878d1d74790e40752d414b Mon Sep 17 00:00:00 2001 From: M0RPH3US Date: Wed, 12 Feb 2025 17:12:23 +0530 Subject: [PATCH 5/8] Update slideshowpure.js Fixed Typo --- slideshowpure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slideshowpure.js b/slideshowpure.js index 0b6e207..e436b45 100644 --- a/slideshowpure.js +++ b/slideshowpure.js @@ -32,7 +32,7 @@ const getJellyfinCredentials = () => { return } - return { token: firstServer.AccessToken, userId: firstServer.AccessToken }; + return { token: firstServer.AccessToken, userId: firstServer.UserId }; } catch (e) { console.error('Could not parse jellyfin credentials', e); } From 48ebb488ed833e5b66413df54c566cdaaeb19834 Mon Sep 17 00:00:00 2001 From: M0RPH3US Date: Wed, 12 Feb 2025 17:14:16 +0530 Subject: [PATCH 6/8] Update README.md Removed Session storage code from index.html --- README.md | 57 ------------------------------------------------------- 1 file changed, 57 deletions(-) diff --git a/README.md b/README.md index 7baaf49..761e003 100644 --- a/README.md +++ b/README.md @@ -43,63 +43,6 @@ Here I present my version of the same with some code improvements, loading optim 2. Search for `` 3. Just before the ` - function saveCredentialsToSessionStorage(credentials) { - try { - sessionStorage.setItem( - "json-credentials", - JSON.stringify(credentials) - ); - console.log("Credentials saved to sessionStorage."); - } catch (error) { - console.error("Error saving credentials:", error); - } - } - function saveApiKey(apiKey) { - try { - sessionStorage.setItem("api-key", apiKey); - console.log("API key saved to sessionStorage."); - } catch (error) { - console.error("Error saving API key:", error); - } - } - (function () { - var originalConsoleLog = console.log; - console.log = function (message) { - originalConsoleLog.apply(console, arguments); - if ( - typeof message === "string" && - message.startsWith("Stored JSON credentials:") - ) { - try { - var jsonString = message.substring( - "Stored JSON credentials: ".length - ); - var credentials = JSON.parse(jsonString); - saveCredentialsToSessionStorage(credentials); - } catch (error) { - console.error("Error parsing credentials:", error); - } - } - if ( - typeof message === "string" && - message.startsWith("opening web socket with url:") - ) { - try { - var url = message.split("url:")[1].trim(); - var urlParams = new URL(url).searchParams; - var apiKey = urlParams.get("api_key"); - if (apiKey) { - saveApiKey(apiKey); - } - } catch (error) { - console.error("Error extracting API key:", error); - } - } - }; - })(); - From 8c55e30aad52c932d01836e936d291e14d7003ae Mon Sep 17 00:00:00 2001 From: M0RPH3US Date: Wed, 12 Feb 2025 19:13:08 +0530 Subject: [PATCH 7/8] Add files via upload ToDo - Show Slides after navigation --- slideshowpure.js | 48 ++++++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/slideshowpure.js b/slideshowpure.js index e436b45..02eec0e 100644 --- a/slideshowpure.js +++ b/slideshowpure.js @@ -1,4 +1,3 @@ - /** * Credentials for a given server * @typedef {Object} ServerCredentials @@ -17,7 +16,7 @@ * @returns {{token: string, userId: string}} Credential resource */ const getJellyfinCredentials = () => { - const jellyfinCreds = localStorage.getItem('jellyfin_credentials'); + const jellyfinCreds = localStorage.getItem("jellyfin_credentials"); try { /** @@ -28,17 +27,22 @@ const getJellyfinCredentials = () => { const firstServer = serverCredentials.Servers[0]; if (!firstServer) { - console.error('Could not find credentials for the client'); - return + console.error("Could not find credentials for the client"); + return; } return { token: firstServer.AccessToken, userId: firstServer.UserId }; } catch (e) { - console.error('Could not parse jellyfin credentials', e); + console.error("Could not parse jellyfin credentials", e); } -} +}; const slidesInit = () => { + if (window.hasInitializedSlideshow) { + console.log("Slideshow already initialized. Skipping re-init."); + return; + } + window.hasInitializedSlideshow = true; const shuffleInterval = 8000; let isTransitioning = false; const listFileName = `${window.location.origin}/web/avatars/list.txt`; @@ -156,7 +160,7 @@ const slidesInit = () => { const tomatoLogo = document.createElement("img"); tomatoLogo.className = "tomato-logo"; tomatoLogo.src = - "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; + "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; const criticLogo = document.createElement("img"); criticLogo.className = "critic-logo"; @@ -331,14 +335,11 @@ const slidesInit = () => { } }; const fetchItemDetails = async (itemId) => { - const response = await fetch( - `${window.location.origin}/Users/${userId}/Items/${itemId}`, - { - headers: { - Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, - }, - } - ); + const response = await fetch(`${window.location.origin}/Items/${itemId}`, { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + }); const item = await response.json(); console.log("Item Title:", item.Name); return item; @@ -362,7 +363,7 @@ const slidesInit = () => { const fetchItemsFromServer = async () => { try { const response = await fetch( - `${window.location.origin}/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&isPlayed=False&Limit=150`, + `${window.location.origin}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&SortBy=random&isPlayed=False&Limit=500`, { headers: { Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, @@ -412,6 +413,8 @@ const slidesInit = () => { const dots = document.querySelectorAll(".dot"); slides.forEach((slide, i) => { if (i === index) { + console.log(`Showing slide ${index}`); // Debugging + slide.style.removeProperty("display"); slide.style.display = "block"; slide.offsetHeight; slide.style.opacity = "1"; @@ -423,6 +426,7 @@ const slidesInit = () => { slide.style.display = "none"; }, 500); } + document.getElementById("slides-container").offsetHeight; }); dots.forEach((dot, i) => { dot.classList.toggle("active", i === index % 5); @@ -531,15 +535,3 @@ const slidesInit = () => { }; initializeSlides(); }; -document.addEventListener("DOMContentLoaded", () => { - document.body.addEventListener("click", (event) => { - const homeButton = event.target.matches( - ".headerHomeButton, .css-17c09up, .mainDrawer-scrollContainer > a:nth-child(2)" - ); - if (homeButton) { - event.preventDefault(); - window.location.href = "/web/index.html#/home.html"; - setTimeout(handleHomeNavigation, 300); - } - }); -}); From eb6d076d4cc44e4b7b13dc6cbe3b7e749585762b Mon Sep 17 00:00:00 2001 From: M0RPH3US Date: Wed, 12 Feb 2025 20:41:04 +0530 Subject: [PATCH 8/8] Update README.md Temporary Fix for Navigation --- README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/README.md b/README.md index 761e003..2e7e55c 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,43 @@ Here I present my version of the same with some code improvements, loading optim 2. Search for `` 3. Just before the ` + (function () { + // List of CSS selectors for Home buttons + const buttonSelectors = [ + ".headerHomeButton.barsMenuButton", + ".css-17c09up", + ".mainDrawer-scrollContainer > a:nth-child(2)" + ]; + + // Polling interval to check for buttons + const intervalId = setInterval(function () { + buttonSelectors.forEach(selector => { + // Try to find the button + const homeButton = document.querySelector(selector); + + // If the button is found + if (homeButton) { + // Attach the click event listener if not already added + if (!homeButton.hasAttribute("data-home-handler")) { + homeButton.addEventListener("click", function (event) { + event.preventDefault(); // Prevent default behavior if necessary + window.location.href = "/web/index.html#/home.html"; + }); + + // Mark the button as handled + homeButton.setAttribute("data-home-handler", "true"); + } + } + }); + + // Stop polling if all buttons are found + if (buttonSelectors.every(selector => document.querySelector(selector))) { + clearInterval(intervalId); + } + }, 100); // Check every 100ms + })(); +