From 6f2617f942a086100997a1103783d5241c0c5a97 Mon Sep 17 00:00:00 2001 From: HighImKevin Date: Wed, 9 Sep 2026 05:54:42 -0400 Subject: [PATCH] Update authentication headers to support v12 requirements, also add v12 skin support to skins from v11 --- injector.js | 42 ++++++++++++++++--------- scripts/apiHelper.js | 19 +++++------- scripts/configuration.js | 23 +++----------- scripts/deviceManager.js | 2 +- scripts/flattenSingleSeasonShows.js | 4 +-- scripts/headerTabs.js | 4 +-- scripts/homeScreen.js | 30 +++++++++--------- scripts/removeContinue.js | 4 +-- scripts/seriesEpisodes.js | 33 ++++++++++---------- scripts/skinConfig.js | 24 +++++++-------- scripts/skinManager.js | 11 +++++-- scripts/subtitleSearch.js | 14 ++++----- scripts/updoot.js | 4 +-- scripts/utils.js | 26 ++-------------- scripts/watchlist.js | 48 ++++++++++++++--------------- 15 files changed, 135 insertions(+), 153 deletions(-) diff --git a/injector.js b/injector.js index c257b56..b0b16c5 100644 --- a/injector.js +++ b/injector.js @@ -7,6 +7,22 @@ console.log('[KefinTweaks Injector] Initializing...'); + function getAuthHeader() { + const token = ApiClient.accessToken(); + const client = typeof ApiClient.applicationName === 'function' ? ApiClient.applicationName() : 'Jellyfin Web'; + const device = typeof ApiClient.deviceName === 'function' ? ApiClient.deviceName() : (navigator.userAgent.includes('Chrome') ? 'Chrome' : 'Browser'); + const deviceId = typeof ApiClient.deviceId === 'function' ? ApiClient.deviceId() : ''; + const version = ApiClient._appVersion || ApiClient._serverVersion || ''; + const parts = [ + `Client="${encodeURIComponent(client)}"`, + `Device="${encodeURIComponent(device)}"`, + `DeviceId="${encodeURIComponent(deviceId)}"`, + `Version="${encodeURIComponent(version)}"`, + `Token="${encodeURIComponent(token)}"` + ]; + return `MediaBrowser ${parts.join(', ')}`; + } + // Check for global configuration reset try { const config = window.KefinTweaksConfig || {}; @@ -110,7 +126,7 @@ name: 'utils', script: 'utils.js', css: null, - dependencies: [], + dependencies: ['apiHelper'], description: 'Common utilities for page view management and MutationObserver conversion' }, { @@ -131,7 +147,7 @@ name: 'skinManager', script: 'skinManager.js', css: 'defaultSkin.css', - dependencies: ['utils', 'skinConfigLegacyDefaults', 'skinConfig', 'modal'], + dependencies: ['utils', 'skinConfigLegacyDefaults', 'skinConfig', 'modal', 'apiHelper'], priority: true, // Load immediately after dependencies to reduce UI disruption description: 'Adds skin selection dropdown to display preferences page and manages skin CSS loading' }, @@ -202,7 +218,7 @@ name: 'headerTabs', script: 'headerTabs.js', css: null, - dependencies: [], + dependencies: ['apiHelper'], description: 'Header tab improvements' }, { @@ -230,7 +246,7 @@ name: 'updoot', script: 'updoot.js', css: null, - dependencies: [], + dependencies: ['apiHelper'], description: 'Upvote functionality provided by https://github.com/BobHasNoSoul/jellyfin-updoot' }, { @@ -251,14 +267,14 @@ name: 'removeContinue', script: 'removeContinue.js', css: null, - dependencies: [], + dependencies: ['apiHelper'], description: 'Adds remove from continue watching functionality to cards with data-position-ticks' }, { name: 'subtitleSearch', script: 'subtitleSearch.js', css: 'subtitleSearch.css', - dependencies: ['toaster'], + dependencies: ['toaster', 'apiHelper'], description: 'Adds subtitle search functionality to the video OSD, allowing users to search and download subtitles from remote sources' }, { @@ -624,7 +640,7 @@ } async function loadConfigurationJS() { - const configDependencyNames = ['modal', 'toaster', 'utils', 'apiHelper', 'indexedDBCache']; + const configDependencyNames = ['modal', 'toaster', 'apiHelper', 'utils', 'indexedDBCache']; for (const depName of configDependencyNames) { const depScript = SCRIPT_DEFINITIONS.find(script => script.name === depName); if (depScript) { @@ -839,11 +855,10 @@ } const server = ApiClient._serverAddress; - const token = ApiClient.accessToken(); const response = await fetch(`${server}/Plugins`, { headers: { - 'X-Emby-Token': token + 'Authorization': getAuthHeader() } }); @@ -870,11 +885,10 @@ } const server = ApiClient._serverAddress; - const token = ApiClient.accessToken(); const response = await fetch(`${server}/Plugins/${pluginId}/Configuration`, { headers: { - 'X-Emby-Token': token + 'Authorization': getAuthHeader() } }); @@ -897,12 +911,11 @@ } const server = ApiClient._serverAddress; - const token = ApiClient.accessToken(); const response = await fetch(`${server}/Plugins/${pluginId}/Configuration`, { method: 'POST', headers: { - 'X-Emby-Token': token, + 'Authorization': getAuthHeader(), 'Content-Type': 'application/json' }, body: JSON.stringify(config) @@ -956,11 +969,10 @@ // Get current CustomTabs config const server = ApiClient._serverAddress; - const token = ApiClient.accessToken(); const response = await fetch(`${server}/CustomTabs/Config`, { headers: { - 'X-Emby-Token': token + 'Authorization': getAuthHeader() } }); diff --git a/scripts/apiHelper.js b/scripts/apiHelper.js index e62c768..c614e2e 100644 --- a/scripts/apiHelper.js +++ b/scripts/apiHelper.js @@ -21,7 +21,6 @@ async function getActiveSession() { ensureApiClient(); - const token = ApiClient.accessToken(); const serverUrl = ApiClient.serverAddress(); const deviceId = ApiClient.deviceId(); @@ -39,7 +38,7 @@ method: 'GET', headers: { 'Accept': 'application/json', - 'Authorization': `MediaBrowser Token="${token}"` + 'Authorization': getAuthHeader() } }); @@ -71,7 +70,6 @@ const session = await getActiveSession(); const sessionId = session.Id; - const token = ApiClient.accessToken(); const serverUrl = ApiClient.serverAddress(); const limitedIds = ids.slice(0, MAX_PLAY_NOW_IDS); @@ -102,7 +100,7 @@ const response = await fetch(`${serverUrl}/Sessions/${sessionId}/Playing?${params.toString()}`, { method: 'POST', headers: { - 'X-Emby-Token': token, + 'Authorization': getAuthHeader(), } }); @@ -115,7 +113,6 @@ const session = await getActiveSession(); const sessionId = session.Id; - const token = ApiClient.accessToken(); const serverUrl = ApiClient.serverAddress(); const params = new URLSearchParams({ @@ -131,7 +128,7 @@ const response = await fetch(`${serverUrl}/Sessions/${sessionId}/Playing?${params.toString()}`, { method: 'POST', headers: { - 'X-Emby-Token': token + 'Authorization': getAuthHeader() } }); @@ -276,10 +273,9 @@ // Fetch from API try { - const token = ApiClient.accessToken(); const response = await fetch(url, { headers: { - 'X-Emby-Token': token, + 'Authorization': getAuthHeader(), 'Accept': 'application/json' } }); @@ -426,7 +422,7 @@ method: 'POST', headers: { 'Content-Type': 'application/json', - 'X-Emby-Token': ApiClient.accessToken() + 'Authorization': getAuthHeader() }, body: JSON.stringify({ Rating: rating, @@ -502,7 +498,6 @@ throw new Error('No active session found'); } - const token = ApiClient.accessToken(); const serverUrl = ApiClient.serverAddress(); // Build NowPlayingQueue array - each item needs at least an Id @@ -522,7 +517,7 @@ method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `MediaBrowser Token="${token}"` + 'Authorization': getAuthHeader() }, body: JSON.stringify(payload) }); @@ -555,6 +550,8 @@ return await response.json(); }, + + getAuthHeader, }; // Expose apiHelper to global window object diff --git a/scripts/configuration.js b/scripts/configuration.js index ca8b25a..1a47b9c 100644 --- a/scripts/configuration.js +++ b/scripts/configuration.js @@ -3782,7 +3782,7 @@ const token = window.ApiClient.accessToken(); const response = await fetch(`${serverAddress}/Users/${userId}/Items?IncludeItemTypes=BoxSet,CollectionFolder&Recursive=true&Fields=ItemCounts`, { - headers: { 'X-Emby-Token': token } + headers: { 'Authorization': apiHelper.getAuthHeader() } }); if (!response.ok) { @@ -3902,7 +3902,7 @@ const token = window.ApiClient.accessToken(); const response = await fetch(`${serverAddress}/Users/${userId}/Items?IncludeItemTypes=Playlist&Recursive=true&Fields=ItemCounts`, { - headers: { 'X-Emby-Token': token } + headers: { 'Authorization': apiHelper.getAuthHeader() } }); if (!response.ok) { @@ -4022,7 +4022,7 @@ const token = window.ApiClient.accessToken(); const response = await fetch(`${serverAddress}/Users/${userId}/Items?IncludeItemTypes=BoxSet,CollectionFolder&Recursive=true&Fields=ItemCounts`, { - headers: { 'X-Emby-Token': token } + headers: { 'Authorization': apiHelper.getAuthHeader() } }); if (!response.ok) { @@ -4160,7 +4160,7 @@ const token = window.ApiClient.accessToken(); const response = await fetch(`${serverAddress}/Users/${userId}/Items?IncludeItemTypes=Playlist&Recursive=true&Fields=ItemCounts`, { - headers: { 'X-Emby-Token': token } + headers: { 'Authorization': apiHelper.getAuthHeader() } }); if (!response.ok) { @@ -4738,23 +4738,10 @@ } const server = ApiClient._serverAddress; - const token = ApiClient.accessToken(); - const client = typeof ApiClient.applicationName === 'function' ? ApiClient.applicationName() : 'Jellyfin Web'; - const device = typeof ApiClient.deviceName === 'function' ? ApiClient.deviceName() : (navigator.userAgent.includes('Chrome') ? 'Chrome' : 'Browser'); - const deviceId = typeof ApiClient.deviceId === 'function' ? ApiClient.deviceId() : ''; - const version = ApiClient._appVersion || ApiClient._serverVersion || ''; - const parts = [ - `Client="${encodeURIComponent(client)}"`, - `Device="${encodeURIComponent(device)}"`, - `DeviceId="${encodeURIComponent(deviceId)}"`, - `Version="${encodeURIComponent(version)}"`, - `Token="${encodeURIComponent(token)}"` - ]; - const auth = `MediaBrowser ${parts.join(', ')}`; const response = await fetch(`${server}/Plugins/${pluginId}/Configuration`, { headers: { - 'Authorization': auth + 'Authorization': apiHelper.getAuthHeader() } }); diff --git a/scripts/deviceManager.js b/scripts/deviceManager.js index e36a7e5..95cf017 100644 --- a/scripts/deviceManager.js +++ b/scripts/deviceManager.js @@ -132,7 +132,7 @@ method: 'DELETE', headers: { 'Accept': 'application/json', - 'Authorization': `MediaBrowser Token="${token}"` + 'Authorization': apiHelper.getAuthHeader() } }); diff --git a/scripts/flattenSingleSeasonShows.js b/scripts/flattenSingleSeasonShows.js index 7553b8c..6ddd929 100644 --- a/scripts/flattenSingleSeasonShows.js +++ b/scripts/flattenSingleSeasonShows.js @@ -24,7 +24,7 @@ LOG(`Fetching Next Up episode for series: ${seriesId}`); const nextUpUrl = `${serverUrl}/Shows/NextUp?SeriesId=${seriesId}&UserId=${userId}&Fields=MediaSourceCount`; const nextUpRes = await fetch(nextUpUrl, { - headers: { "Authorization": `MediaBrowser Token="${token}"` } + headers: { "Authorization": apiHelper.getAuthHeader() } }); if (!nextUpRes.ok) { @@ -69,7 +69,7 @@ LOG(`Fetching episodes for series: ${seriesId}`); const episodesUrl = `${serverUrl}/Shows/${seriesId}/Episodes?UserId=${userId}&Fields=UserData`; const episodesRes = await fetch(episodesUrl, { - headers: { "Authorization": `MediaBrowser Token="${token}"` } + headers: { "Authorization": apiHelper.getAuthHeader() } }); if (!episodesRes.ok) { diff --git a/scripts/headerTabs.js b/scripts/headerTabs.js index 3bf311a..4bef6d2 100644 --- a/scripts/headerTabs.js +++ b/scripts/headerTabs.js @@ -93,7 +93,7 @@ `${serverAddress}/DisplayPreferences/usersettings?userId=${userId}&client=emby`, { headers: { - 'X-Emby-Token': accessToken + 'Authorization': apiHelper.getAuthHeader() } } ); @@ -345,7 +345,7 @@ method: "GET", headers: { "Content-Type": "application/json", - "X-Emby-Token": ApiClient._serverInfo.AccessToken || ApiClient.accessToken(), + "Authorization": apiHelper.getAuthHeader(), }, }); const customTabs = await response.json(); diff --git a/scripts/homeScreen.js b/scripts/homeScreen.js index 0059926..0f7e147 100644 --- a/scripts/homeScreen.js +++ b/scripts/homeScreen.js @@ -1112,7 +1112,7 @@ try { const response = await fetch(url, { headers: { - "Authorization": `MediaBrowser Token="${token}"` + "Authorization": apiHelper.getAuthHeader() } }); @@ -1552,7 +1552,7 @@ try { const response = await fetch(url, { headers: { - 'Authorization': `MediaBrowser Token="${ApiClient.accessToken()}"` + 'Authorization': apiHelper.getAuthHeader() } }); @@ -1649,7 +1649,7 @@ try { const response = await fetch(`${serverUrl}/Genres?IncludeItemTypes=Movie`, { headers: { - "Authorization": `MediaBrowser Token="${token}"` + "Authorization": apiHelper.getAuthHeader() } }); @@ -1745,7 +1745,7 @@ queries.map(async (query) => { const response = await fetch(query.url, { headers: { - "Authorization": `MediaBrowser Token="${token}"` + "Authorization": apiHelper.getAuthHeader() } }); @@ -1854,7 +1854,7 @@ const response = await fetch(url, { headers: { - "Authorization": `MediaBrowser Token="${token}"` + "Authorization": apiHelper.getAuthHeader() } }); @@ -2214,7 +2214,7 @@ try { const response = await fetch(url, { headers: { - "Authorization": `MediaBrowser Token="${token}"` + "Authorization": apiHelper.getAuthHeader() } }); @@ -2251,7 +2251,7 @@ try { const response = await fetch(url, { headers: { - "Authorization": `MediaBrowser Token="${token}"` + "Authorization": apiHelper.getAuthHeader() } }); @@ -2565,7 +2565,7 @@ const studiosUrl = `${serverUrl}/Studios?SortBy=SortName&SortOrder=Ascending&IncludeItemTypes=Series&Recursive=true&Fields=DateCreated%2CPrimaryImageAspectRatio&StartIndex=0&ParentId=${library.Id}&userId=${userId}${filterParam}`; const response = await fetch(studiosUrl, { headers: { - "Authorization": `MediaBrowser Token="${token}"` + "Authorization": apiHelper.getAuthHeader() } }); @@ -2646,7 +2646,7 @@ const response = await fetch(url, { headers: { - "Authorization": `MediaBrowser Token="${token}"` + "Authorization": apiHelper.getAuthHeader() } }); @@ -2680,7 +2680,7 @@ const response = await fetch(url, { headers: { - "Authorization": `MediaBrowser Token="${token}"` + "Authorization": apiHelper.getAuthHeader() } }); @@ -3828,7 +3828,7 @@ const movieUrl = `${serverUrl}/Users/${userId}/Items/${recentMovie.Id}?Fields=People`; const movieResponse = await fetch(movieUrl, { headers: { - "Authorization": `MediaBrowser Token="${token}"` + "Authorization": apiHelper.getAuthHeader() } }); @@ -3890,7 +3890,7 @@ const movieUrl = `${serverUrl}/Users/${userId}/Items/${recentMovie.Id}?Fields=People`; const movieResponse = await fetch(movieUrl, { headers: { - "Authorization": `MediaBrowser Token="${token}"` + "Authorization": apiHelper.getAuthHeader() } }); @@ -3958,7 +3958,7 @@ const movieUrl = `${serverUrl}/Users/${userId}/Items/${recentMovie.Id}?Fields=People`; const movieResponse = await fetch(movieUrl, { headers: { - "Authorization": `MediaBrowser Token="${token}"` + "Authorization": apiHelper.getAuthHeader() } }); @@ -4708,7 +4708,7 @@ const response = await fetch(url, { headers: { - "Authorization": `MediaBrowser Token="${token}"` + "Authorization": apiHelper.getAuthHeader() } }); @@ -5391,7 +5391,7 @@ const serverAddress = ApiClient.serverAddress(); const token = ApiClient.accessToken(); const genresResponse = await fetch(`${serverAddress}/Genres?IncludeItemTypes=Movie`, { - headers: { "Authorization": `MediaBrowser Token="${token}"` } + headers: { "Authorization": apiHelper.getAuthHeader() } }); if (!genresResponse.ok) { diff --git a/scripts/removeContinue.js b/scripts/removeContinue.js index b9e69d1..c14b10f 100644 --- a/scripts/removeContinue.js +++ b/scripts/removeContinue.js @@ -162,7 +162,7 @@ const response = await fetch(userDataUrl, { method: 'GET', headers: { - "Authorization": `MediaBrowser Token="${token}"`, + "Authorization": apiHelper.getAuthHeader(), "Content-Type": "application/json" } }); @@ -196,7 +196,7 @@ const response = await fetch(userDataUrl, { method: 'POST', headers: { - "Authorization": `MediaBrowser Token="${token}"`, + "Authorization": apiHelper.getAuthHeader(), "Content-Type": "application/json" }, body: JSON.stringify(updatedUserData) diff --git a/scripts/seriesEpisodes.js b/scripts/seriesEpisodes.js index 689fd31..098d386 100644 --- a/scripts/seriesEpisodes.js +++ b/scripts/seriesEpisodes.js @@ -24,7 +24,7 @@ LOG(`Fetching Next Up episode for series: ${seriesId}`); const nextUpUrl = `${serverUrl}/Shows/NextUp?SeriesId=${seriesId}&UserId=${userId}&Fields=MediaSourceCount`; const nextUpRes = await fetch(nextUpUrl, { - headers: { "Authorization": `MediaBrowser Token="${token}"` } + headers: { "Authorization": apiHelper.getAuthHeader() } }); if (!nextUpRes.ok) { @@ -63,7 +63,7 @@ LOG(`Fetching season data for season ID: ${seasonId}`); const seasonUrl = `${serverUrl}/Items/${seasonId}?UserId=${userId}`; const seasonRes = await fetch(seasonUrl, { - headers: { "Authorization": `MediaBrowser Token="${token}"` } + headers: { "Authorization": apiHelper.getAuthHeader() } }); if (!seasonRes.ok) { @@ -95,7 +95,7 @@ LOG(`Fetching episodes for series: ${seriesId}${seasonId ? ` (Season ID: ${seasonId})` : ''}`); const episodesUrl = `${serverUrl}/Shows/${seriesId}/Episodes?UserId=${userId}&Fields=UserData`; const episodesRes = await fetch(episodesUrl, { - headers: { "Authorization": `MediaBrowser Token="${token}"` } + headers: { "Authorization": apiHelper.getAuthHeader() } }); if (!episodesRes.ok) { @@ -559,18 +559,17 @@ // Insert episodes section right after NextUp section (or at the beginning if no NextUp) const detailPageContent = activePage.querySelector('.detailPageContent'); - if (detailPageContent) { - if (nextUpSection && nextUpSection.parentNode) { - nextUpSection.parentNode.insertBefore(seasonSection, nextUpSection.nextSibling); - } else { - // Find a good insertion point - after NextUp or before childrenCollapsible - const childrenCollapsible = activePage.querySelector('.detailSection #listChildrenCollapsible') || - activePage.querySelector('#childrenCollapsible'); - if (childrenCollapsible && childrenCollapsible.parentNode) { - childrenCollapsible.parentNode.insertBefore(seasonSection, childrenCollapsible); - } else { - detailPageContent.insertBefore(seasonSection, detailPageContent.firstChild); - } + if (nextUpSection && nextUpSection.parentNode) { + nextUpSection.parentNode.insertBefore(seasonSection, nextUpSection.nextSibling); + } else { + // Find a good insertion point - after NextUp or before childrenCollapsible + const childrenCollapsible = activePage.querySelector('.detailSection #listChildrenCollapsible') || + activePage.querySelector('#childrenCollapsible'); + if (childrenCollapsible && childrenCollapsible.parentNode) { + childrenCollapsible.parentNode.insertBefore(seasonSection, childrenCollapsible); + } + else if (detailPageContent){ + detailPageContent.insertBefore(seasonSection, detailPageContent.firstChild); } } @@ -817,7 +816,9 @@ } // Mark as processed - activePage.dataset.seriesEpisodesProcessed = 'true'; + if (activePage.querySelector('.series-episodes-section')) { + activePage.dataset.seriesEpisodesProcessed = 'true'; + } } /** diff --git a/scripts/skinConfig.js b/scripts/skinConfig.js index 3ea2155..91b8d60 100644 --- a/scripts/skinConfig.js +++ b/scripts/skinConfig.js @@ -107,7 +107,7 @@ ] }, { - majorServerVersions: [11], + majorServerVersions: [11, 12], urls: [ 'https://cdn.jsdelivr.net/gh/lscambo13/ElegantFin@main/Theme/ElegantFin-jellyfin-theme-build-latest-minified.css', `${getKefinTweaksRoot()}skins/elegant-kefin.css`, @@ -159,7 +159,7 @@ author: 'KartoffelChipss', url: [ { - majorServerVersions: [10, 11], + majorServerVersions: [10, 11, 12], urls: [ 'https://cdn.jsdelivr.net/gh/KartoffelChipss/NeutralFin@latest/theme/neutralfin-minified.css', 'https://cdn.jsdelivr.net/gh/KartoffelChipss/Jellyfin-Lucide@main/theme/jellyfin-lucide.css', @@ -211,7 +211,7 @@ author: 'KBH-Reeper', url: [ { - majorServerVersions: [10, 11], + majorServerVersions: [10, 11, 12], urls: [ 'https://cdn.jsdelivr.net/gh/KBH-Reeper/GlassFin@latest/Theme/GlassFin-Theme-latest-stable.css', 'https://cdn.jsdelivr.net/gh/KBH-Reeper/GlassFin@latest/Theme/PluginStyling/MediaBar-Plugin-latest.css', @@ -296,7 +296,7 @@ urls: ['https://cdn.jsdelivr.net/gh/ranaldsgift/Jellypane@main/jellypane.css'] }, { - majorServerVersions: [11], + majorServerVersions: [11, 12], urls: ['https://cdn.jsdelivr.net/gh/tedhinklater/Jellypane@main/jellypane.css'] }, ], @@ -311,7 +311,7 @@ urls: ['https://cdn.jsdelivr.net/gh/tedhinklater/finimalism@main/finimalism7.css'] }, { - majorServerVersions: [11], + majorServerVersions: [11, 12], urls: ['https://cdn.jsdelivr.net/gh/tedhinklater/finimalism@main/finimalism10.11.css'] } ], @@ -338,7 +338,7 @@ urls: ['https://cdn.jsdelivr.net/gh/n00bcodr/jellyfish@main/theme.css'] }, { - majorServerVersions: [11], + majorServerVersions: [11, 12], urls: [ 'https://cdn.jsdelivr.net/gh/n00bcodr/jellyfish@main/theme.css', 'https://cdn.jsdelivr.net/gh/n00bcodr/jellyfish@main/10.11_fixes.css' @@ -428,7 +428,7 @@ author: 'alexyle', url: [ { - majorServerVersions: [10, 11], + majorServerVersions: [10, 11, 12], urls: ['https://cdn.jsdelivr.net/gh/alexyle/jellyfin-theme@main/glassmorphism/theme.css'] } ], @@ -491,7 +491,7 @@ ] }, { - majorServerVersions: [11], + majorServerVersions: [11, 12], urls: [ 'https://cdn.jsdelivr.net/gh/JamsRepos/Jamfin@latest/theme/complete.css', `${getKefinTweaksRoot()}skins/jamfin-kefin.css` @@ -539,7 +539,7 @@ ] }, { - majorServerVersions: [11], + majorServerVersions: [11, 12], urls: [ 'https://cdn.jsdelivr.net/gh/loof2736/scyfin@latest/CSS/scyfin-theme.css', `${getKefinTweaksRoot()}skins/scyfin-kefin.css` @@ -581,7 +581,7 @@ author: 'Catppuccin', url: [ { - majorServerVersions: [10, 11], + majorServerVersions: [10, 11, 12], urls: ['https://jellyfin.catppuccin.com/theme.css'] } ], @@ -616,7 +616,7 @@ ] }, { - majorServerVersions: [11], + majorServerVersions: [11, 12], urls: [ 'https://cdn.jsdelivr.net/gh/LitCastVlog/Flow@main/CSS/ScyFlow-oneliner.css', 'https://cdn.jsdelivr.net/gh/LitCastVlog/Flow@main/CSS/ScyFlow-Compatibility.css', @@ -719,7 +719,7 @@ author: 'CTalvio', url: [ { - majorServerVersions: [10, 11], + majorServerVersions: [10, 11, 12], urls: [ 'https://cdn.jsdelivr.net/gh/CTalvio/Ultrachromic/base.css', 'https://cdn.jsdelivr.net/gh/CTalvio/Ultrachromic/accentlist.css', diff --git a/scripts/skinManager.js b/scripts/skinManager.js index 8174fa3..250c0b2 100644 --- a/scripts/skinManager.js +++ b/scripts/skinManager.js @@ -26,6 +26,11 @@ function getMajorServerVersion(version) { const versionParts = version.split('.'); + const firstPartInt = parseInt(versionParts[0], 10); + if (firstPartInt >= 12) { + return 12; + } + if (versionParts.length >= 2) { const majorVersion = parseInt(versionParts[1], 10); if (!isNaN(majorVersion)) { @@ -360,7 +365,7 @@ // Find JavaScript Injector plugin const pluginsResponse = await fetch(`${server}/Plugins`, { - headers: { 'X-Emby-Token': token } + headers: { 'Authorization': apiHelper.getAuthHeader() } }); if (!pluginsResponse.ok) { @@ -382,7 +387,7 @@ // Get current injector config const configUrl = `${server}/Plugins/${pluginId}/Configuration`; const configResponse = await fetch(configUrl, { - headers: { 'X-Emby-Token': token } + headers: { 'Authorization': apiHelper.getAuthHeader() } }); if (!configResponse.ok) { @@ -421,7 +426,7 @@ window.KefinTweaksConfig = ${JSON.stringify(configToBackup, null, 2)};`; const saveResponse = await fetch(configUrl, { method: 'POST', headers: { - 'X-Emby-Token': token, + 'Authorization': apiHelper.getAuthHeader(), 'Content-Type': 'application/json' }, body: JSON.stringify(injectorConfig) diff --git a/scripts/subtitleSearch.js b/scripts/subtitleSearch.js index 890da95..d692397 100644 --- a/scripts/subtitleSearch.js +++ b/scripts/subtitleSearch.js @@ -338,7 +338,7 @@ method: 'GET', headers: { 'Accept': 'application/json', - 'Authorization': `MediaBrowser Token="${token}"` + 'Authorization': apiHelper.getAuthHeader() } }); @@ -425,7 +425,7 @@ method: 'GET', headers: { 'Accept': 'application/json', - 'Authorization': `MediaBrowser Token="${token}"` + 'Authorization': apiHelper.getAuthHeader() } }); @@ -512,7 +512,7 @@ method: 'GET', headers: { 'Accept': 'application/json', - 'Authorization': `MediaBrowser Token="${token}"` + 'Authorization': apiHelper.getAuthHeader() } }); @@ -588,7 +588,7 @@ method: 'GET', headers: { 'Accept': 'application/json', - 'Authorization': `MediaBrowser Token="${token}"` + 'Authorization': apiHelper.getAuthHeader() } }); @@ -638,7 +638,7 @@ const response = await fetch(`${serverUrl}/Sessions/${sessionId}/Command`, { method: 'POST', headers: { - 'X-Emby-Token': token, + 'Authorization': apiHelper.getAuthHeader(), 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -788,7 +788,7 @@ const commandResponse = await fetch(`${serverUrl}/Sessions/${sessionId}/Playing?${params.toString()}`, { method: 'POST', headers: { - 'X-Emby-Token': token + 'Authorization': apiHelper.getAuthHeader() } }); @@ -903,7 +903,7 @@ const response = await fetch(`${serverUrl}/Items/${itemId}/RemoteSearch/Subtitles/${subId}`, { method: 'POST', headers: { - 'Authorization': `MediaBrowser Token="${token}"` + 'Authorization': apiHelper.getAuthHeader() } }); diff --git a/scripts/updoot.js b/scripts/updoot.js index cc97523..2b792a6 100644 --- a/scripts/updoot.js +++ b/scripts/updoot.js @@ -29,11 +29,11 @@ async function fetchItemDetails(itemId) { console.log('Fetching item details for itemId:', itemId); try { - const url = `${serverUrl}/Items/${itemId}?api_key=${apiKey}`; // Fixed typo from original: 'melalui' to 'apiKey' + const url = `${serverUrl}/Items/${itemId}`; console.log('Requesting:', url); const response = await fetch(url, { method: 'GET', - headers: { 'X-Emby-Token': apiKey } + headers: { 'Authorization': apiHelper.getAuthHeader() } }); if (!response.ok) { console.error('Fetch item details failed:', `HTTP ${response.status}`); diff --git a/scripts/utils.js b/scripts/utils.js index 78c2b6a..2155ab9 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -51,26 +51,6 @@ } } - /** - * Build MediaBrowser Authorization header - * @returns {string} - Authorization header - */ - function getAuthHeader() { - const token = ApiClient.accessToken(); - const client = typeof ApiClient.applicationName === 'function' ? ApiClient.applicationName() : 'Jellyfin Web'; - const device = typeof ApiClient.deviceName === 'function' ? ApiClient.deviceName() : (navigator.userAgent.includes('Chrome') ? 'Chrome' : 'Browser'); - const deviceId = typeof ApiClient.deviceId === 'function' ? ApiClient.deviceId() : ''; - const version = ApiClient._appVersion || ApiClient._serverVersion || ''; - const parts = [ - `Client="${encodeURIComponent(client)}"`, - `Device="${encodeURIComponent(device)}"`, - `DeviceId="${encodeURIComponent(deviceId)}"`, - `Version="${encodeURIComponent(version)}"`, - `Token="${encodeURIComponent(token)}"` - ]; - return `MediaBrowser ${parts.join(', ')}`; - } - // Array to store registered handlers const handlers = []; @@ -452,7 +432,7 @@ // Get current injector config const configUrl = `${server}/Plugins/${pluginId}/Configuration`; const configResponse = await fetch(configUrl, { - headers: { 'Authorization': getAuthHeader() } + headers: { 'Authorization': apiHelper.getAuthHeader() } }); if (!configResponse.ok) { @@ -495,7 +475,7 @@ window.KefinTweaksConfig = ${JSON.stringify(configToSave, null, 2)};`; const saveResponse = await fetch(configUrl, { method: 'POST', headers: { - 'Authorization': getAuthHeader(), + 'Authorization': apiHelper.getAuthHeader(), 'Content-Type': 'application/json' }, body: JSON.stringify(injectorConfig) @@ -579,7 +559,7 @@ window.KefinTweaksConfig = ${JSON.stringify(configToSave, null, 2)};`; method: "GET", headers: { "Content-Type": "application/json", - "X-Emby-Token": ApiClient._serverInfo.AccessToken || ApiClient.accessToken(), + "Authorization": apiHelper.getAuthHeader(), }, }); const data = await response.json(); diff --git a/scripts/watchlist.js b/scripts/watchlist.js index d473095..46d97d3 100644 --- a/scripts/watchlist.js +++ b/scripts/watchlist.js @@ -1263,13 +1263,13 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: try { const episodeUrl = `${serverUrl}/Items/${episodeId}?UserId=${userId}&Fields=ParentId`; - const episodeRes = await fetch(episodeUrl, { headers: { "Authorization": `MediaBrowser Token=\"${token}\"` } }); + const episodeRes = await fetch(episodeUrl, { headers: { "Authorization": apiHelper.getAuthHeader() } }); const episodeData = await episodeRes.json(); // Get the series ID from the episode's parent (season) parent if (episodeData.ParentId) { const seasonUrl = `${serverUrl}/Items/${episodeData.ParentId}?UserId=${userId}&Fields=ParentId`; - const seasonRes = await fetch(seasonUrl, { headers: { "Authorization": `MediaBrowser Token=\"${token}\"` } }); + const seasonRes = await fetch(seasonUrl, { headers: { "Authorization": apiHelper.getAuthHeader() } }); const seasonData = await seasonRes.json(); return seasonData.ParentId || null; @@ -1351,7 +1351,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: try { // Fetch the series item const seriesUrl = `${serverUrl}/Items/${seriesId}?UserId=${userId}&Fields=UserData,RecursiveItemCount&EnableImageTypes=Primary,Banner`; - const seriesRes = await fetch(seriesUrl, { headers: { "Authorization": `MediaBrowser Token=\"${token}\"` } }); + const seriesRes = await fetch(seriesUrl, { headers: { "Authorization": apiHelper.getAuthHeader() } }); const series = await seriesRes.json(); if (!series || !series.Id) { @@ -2505,7 +2505,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: const seriesUrl = `${serverUrl}/Items?IncludeItemTypes=Series&UserId=${userId}&Recursive=true&Fields=UserData,RecursiveItemCount&EnableImageTypes=Primary,Banner`; try { - const seriesRes = await fetch(seriesUrl, { headers: { "Authorization": `MediaBrowser Token=\"${token}\"` } }); + const seriesRes = await fetch(seriesUrl, { headers: { "Authorization": apiHelper.getAuthHeader() } }); const seriesData = await seriesRes.json(); const series = seriesData.Items || []; @@ -2596,7 +2596,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: // Get all episodes including missing ones to get accurate TotalRecordCount const episodesUrl = `${serverUrl}/Shows/${series.Id}/Episodes?UserId=${userId}&Fields=UserData&EnableImageTypes=Primary`; - const episodesRes = await fetch(episodesUrl, { headers: { "Authorization": `MediaBrowser Token=\"${token}\"` } }); + const episodesRes = await fetch(episodesUrl, { headers: { "Authorization": apiHelper.getAuthHeader() } }); const episodesData = await episodesRes.json(); let episodes = episodesData.Items || []; @@ -2736,7 +2736,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: const toggleRes = await fetch(toggleUrl, { method: method, headers: { - "Authorization": `MediaBrowser Token="${token}"` + "Authorization": apiHelper.getAuthHeader() } }); @@ -3803,7 +3803,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: const url = `${serverUrl}/Users/${userId}/Items?IncludeItemTypes=Playlist&Recursive=true&Fields=ChildCount`; const response = await fetch(url, { headers: { - 'Authorization': `MediaBrowser Token="${token}"` + 'Authorization': apiHelper.getAuthHeader() } }); @@ -4039,7 +4039,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: const response = await fetch(`${serverUrl}/Playlists?${params.toString()}`, { method: 'POST', headers: { - 'Authorization': `MediaBrowser Token="${token}"` + 'Authorization': apiHelper.getAuthHeader() } }); @@ -4062,7 +4062,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: // Remove existing entries const existingItemsResponse = await fetch(`${serverUrl}/Playlists/${playlistId}/Items`, { headers: { - 'Authorization': `MediaBrowser Token="${token}"` + 'Authorization': apiHelper.getAuthHeader() } }); @@ -4082,7 +4082,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: const deleteResponse = await fetch(`${serverUrl}/Playlists/${playlistId}/Items?${params.toString()}`, { method: 'DELETE', headers: { - 'Authorization': `MediaBrowser Token="${token}"` + 'Authorization': apiHelper.getAuthHeader() } }); if (!deleteResponse.ok) { @@ -4102,7 +4102,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: const response = await fetch(`${serverUrl}/Playlists/${playlistId}/Items?${params.toString()}`, { method: 'POST', headers: { - 'Authorization': `MediaBrowser Token="${token}"` + 'Authorization': apiHelper.getAuthHeader() } }); if (!response.ok) { @@ -4269,7 +4269,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: if (item.ParentId) { try { const seasonUrl = `${serverUrl}/Items/${item.ParentId}?UserId=${userId}&Fields=Name`; - const seasonRes = await fetch(seasonUrl, { headers: { "Authorization": `MediaBrowser Token=\"${token}\"` } }); + const seasonRes = await fetch(seasonUrl, { headers: { "Authorization": apiHelper.getAuthHeader() } }); const seasonData = await seasonRes.json(); if (seasonData.Name) { exportItem.SeasonName = seasonData.Name; @@ -4405,7 +4405,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: let allLibraryItems = []; try { const url = `${serverUrl}/Items?${queryParams.toString()}`; - const res = await fetch(url, { headers: { "Authorization": `MediaBrowser Token=\"${token}\"` } }); + const res = await fetch(url, { headers: { "Authorization": apiHelper.getAuthHeader() } }); const libraryData = await res.json(); allLibraryItems = libraryData.Items || []; LOG(`Fetched ${allLibraryItems.length} items from library matching import criteria`); @@ -4439,7 +4439,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: for (const type of watchlistTypes) { try { const url = `${serverUrl}/Items?Filters=Likes&IncludeItemTypes=${type}&UserId=${userId}&Recursive=true&Fields=ProviderIds`; - const res = await fetch(url, { headers: { "Authorization": `MediaBrowser Token=\"${token}\"` } }); + const res = await fetch(url, { headers: { "Authorization": apiHelper.getAuthHeader() } }); const watchlistData = await res.json(); const items = watchlistData.Items || []; for (const item of items) { @@ -4547,7 +4547,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: for (const type of types) { try { const url = `${serverUrl}/Items?Filters=Likes&IncludeItemTypes=${type}&UserId=${userId}&Recursive=true&Fields=Id`; - const res = await fetch(url, { headers: { "Authorization": `MediaBrowser Token=\"${token}\"` } }); + const res = await fetch(url, { headers: { "Authorization": apiHelper.getAuthHeader() } }); const data = await res.json(); const items = data.Items || []; @@ -4869,7 +4869,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: try { // Fetch the series item const seriesUrl = `${serverUrl}/Items/${seriesId}?UserId=${userId}&Fields=UserData,RecursiveItemCount&EnableImageTypes=Primary,Banner`; - const seriesRes = await fetch(seriesUrl, { headers: { "Authorization": `MediaBrowser Token=\"${token}\"` } }); + const seriesRes = await fetch(seriesUrl, { headers: { "Authorization": apiHelper.getAuthHeader() } }); const series = await seriesRes.json(); if (!series || !series.Id) { @@ -5251,7 +5251,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: const url = `${serverUrl}/Items?IncludeItemTypes=Movie&UserId=${userId}&Recursive=true&Filters=IsPlayed&Fields=UserData,ProviderIds&EnableImageTypes=Primary,Backdrop,Thumb&ImageTypeLimit=1&SortBy=DatePlayed&SortOrder=Descending`; try { - const res = await fetch(url, { headers: { "Authorization": `MediaBrowser Token=\"${token}\"` } }); + const res = await fetch(url, { headers: { "Authorization": apiHelper.getAuthHeader() } }); const data = await res.json(); const movies = data.Items || []; @@ -6033,7 +6033,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: try { LOG(`Fetching episodes from API 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}\"` } }); + const episodesRes = await fetch(episodesUrl, { headers: { "Authorization": apiHelper.getAuthHeader() } }); const episodesData = await episodesRes.json(); let episodes = episodesData.Items || []; @@ -6203,7 +6203,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: const response = await fetch(markPlayedUrl, { method: isWatched ? 'POST' : 'DELETE', headers: { - "Authorization": `MediaBrowser Token="${token}"`, + "Authorization": apiHelper.getAuthHeader(), "Content-Type": "application/json" } }); @@ -6328,7 +6328,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: const response = await fetch(markPlayedUrl, { method: 'POST', headers: { - "Authorization": `MediaBrowser Token="${token}"`, + "Authorization": apiHelper.getAuthHeader(), "Content-Type": "application/json" } }); @@ -7124,7 +7124,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: method: "GET", headers: { "Content-Type": "application/json", - "X-Emby-Token": ApiClient._serverInfo.AccessToken || ApiClient.accessToken(), + "Authorization": apiHelper.getAuthHeader(), }, }); const data = await response.json(); @@ -7229,7 +7229,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: method: "GET", headers: { "Content-Type": "application/json", - "X-Emby-Token": ApiClient._serverInfo.AccessToken || ApiClient.accessToken(), + "Authorization": apiHelper.getAuthHeader(), }, }) .then((r) => r.json()) @@ -7795,7 +7795,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: const token = apiClient.accessToken(); const url = `${serverUrl}/Items?IncludeItemTypes=Series&UserId=${userId}&Recursive=true&Fields=UserData,RecursiveItemCount`; - const res = await fetch(url, { headers: { "Authorization": `MediaBrowser Token=\"${token}\"` } }); + const res = await fetch(url, { headers: { "Authorization": apiHelper.getAuthHeader() } }); const data = await res.json(); const series = data.Items || []; @@ -7823,7 +7823,7 @@ In the Custom Tabs plugin, add a new tab with the following HTML content: const token = apiClient.accessToken(); fetch(`${serverUrl}/Items?IncludeItemTypes=Series&UserId=${userId}&Recursive=true&Fields=UserData,ChildCount,RecursiveItemCount`, { - headers: { "Authorization": `MediaBrowser Token=\"${token}\"` } + headers: { "Authorization": apiHelper.getAuthHeader() } }) .then(res => res.json()) .then(data => {