diff --git a/main.js b/main.js index 0c95e75..0144dfa 100644 --- a/main.js +++ b/main.js @@ -198,7 +198,6 @@ ipcMain.handle('get-show-details', async (event, showId) => { } // For TVDB v4 API, we need to first get an authentication token - // The token is valid for 1 month let token = null; // Try to get token from cache or login @@ -218,23 +217,51 @@ ipcMain.handle('get-show-details', async (event, showId) => { throw new Error('Failed to authenticate with TVDB API: ' + loginError.message); } - // Since we can't get season information directly from the show details endpoint, - // we'll return basic show information and let the UI handle season loading - // The seasons will be loaded when the user clicks on a season + // Extract numeric ID from the show ID (handle both formats: series-73011 and 73011) + let numericId = showId; + if (showId && showId.startsWith('series-')) { + numericId = showId.split('series-')[1]; + } - return { - success: true, - data: { - id: showId, - name: 'Unknown Show', - status: null, - firstAired: null, - overview: 'Show details not available', - image: null, - slug: null, - seasons: [] + // Get extended show details which includes season information + const response = await axios.get(`https://api4.thetvdb.com/v4/series/${numericId}/extended`, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json' } - }; + }); + + if (response.data && response.data.data) { + const showData = response.data.data; + + // Extract season information properly + let seasons = []; + if (showData.seasons && Array.isArray(showData.seasons)) { + seasons = showData.seasons.map(season => ({ + id: season.id, + number: season.number, + type: season.type?.name || 'Unknown', + episodeCount: season.episodeCount || (season.episodes ? season.episodes.length : 0) + })); + } + + return { + success: true, + data: { + id: showData.id, + name: showData.name, + status: showData.status?.name || showData.status, + firstAired: showData.firstAired, + overview: showData.overview, + image: showData.image, + slug: showData.slug, + seasons: seasons + } + }; + } else { + throw new Error('Invalid response structure from TVDB API'); + } } catch (error) { console.error('TVDB API Error getting show details:', error.message); console.error('Error status:', error.response?.status); @@ -278,48 +305,108 @@ ipcMain.handle('get-season-episodes', async (event, showId, seasonNumber) => { throw new Error('Failed to authenticate with TVDB API: ' + loginError.message); } - // Make API request to get season episodes - // Note: The API endpoint structure may vary, so we'll try different approaches - const response = await axios.get(`https://api4.thetvdb.com/v4/series/${showId}/episodes/default`, { - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }, - params: { - page: 0 - } - }); - - if (response.data && response.data.data) { - // Process episodes - const episodes = response.data.data.episodes ? response.data.data.episodes.map(episode => ({ - id: episode.id, - name: episode.name, - number: episode.number, - seasonNumber: episode.seasonNumber, - aired: episode.aired, - overview: episode.overview, - runtime: episode.runtime - })) : []; - - return { - success: true, - data: { - showId: showId, - seasonNumber: seasonNumber, - episodes: episodes - } - }; - } else { - throw new Error('Invalid response structure from TVDB API'); + // Extract numeric ID from the show ID (handle both formats: series-73011 and 73011) + let numericId = showId; + if (showId && showId.startsWith('series-')) { + numericId = showId.split('series-')[1]; } + + // Try to get episodes for a specific season using the correct endpoint structure + // Based on the API documentation, we should use: /series/{id}/episodes/{season-type} + // where season-type can be "default", "aired", "dvd", etc. + + try { + // First try the season-specific endpoint + const episodesResponse = await axios.get(`https://api4.thetvdb.com/v4/series/${numericId}/episodes/default`, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + params: { + page: 0 + } + }); + + if (episodesResponse.data && episodesResponse.data.data) { + // Process episodes and filter by season number + const episodes = episodesResponse.data.data.episodes ? episodesResponse.data.data.episodes + .filter(episode => episode.seasonNumber === seasonNumber) + .map(episode => ({ + id: episode.id, + name: episode.name, + number: episode.number, + seasonNumber: episode.seasonNumber, + aired: episode.aired, + overview: episode.overview, + runtime: episode.runtime + })) : []; + + return { + success: true, + data: { + showId: showId, + seasonNumber: seasonNumber, + episodes: episodes + } + }; + } + } catch (seasonError) { + console.log('Season-specific endpoint failed, falling back to all episodes:', seasonError.message); + // If season-specific endpoint fails, fall back to getting all episodes and filtering + const episodesResponse = await axios.get(`https://api4.thetvdb.com/v4/series/${numericId}/episodes`, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + params: { + page: 0 + } + }); + + if (episodesResponse.data && episodesResponse.data.data) { + // Process episodes and filter by season number + const episodes = episodesResponse.data.data.episodes ? episodesResponse.data.data.episodes + .filter(episode => episode.seasonNumber === seasonNumber) + .map(episode => ({ + id: episode.id, + name: episode.name, + number: episode.number, + seasonNumber: episode.seasonNumber, + aired: episode.aired, + overview: episode.overview, + runtime: episode.runtime + })) : []; + + return { + success: true, + data: { + showId: showId, + seasonNumber: seasonNumber, + episodes: episodes + } + }; + } + } + + throw new Error('Failed to fetch episodes from both endpoints'); } catch (error) { console.error('TVDB API Error getting season episodes:', error.message); console.error('Error status:', error.response?.status); console.error('Error data:', error.response?.data); - return { success: false, error: 'Failed to fetch season episodes from TVDB API: ' + error.message }; + // Return a more specific error message for debugging + return { + success: false, + error: 'Failed to fetch season episodes from TVDB API: ' + error.message, + debug: { + showId: showId, + seasonNumber: seasonNumber, + status: error.response?.status, + data: error.response?.data + } + }; } }); diff --git a/renderer.js b/renderer.js index 131e4cb..ef7ccfe 100644 --- a/renderer.js +++ b/renderer.js @@ -277,7 +277,7 @@ async function selectShow(show) { } } -// Display seasons +// Display seasons with episode counts function displaySeasons(seasons) { const seasonsContainer = document.getElementById('seasons-container'); seasonsContainer.innerHTML = ''; @@ -294,26 +294,30 @@ function displaySeasons(seasons) { seasons.forEach(season => { const seasonItem = document.createElement('div'); seasonItem.className = 'season-item'; - seasonItem.style.padding = '8px'; - seasonItem.style.margin = '5px 0'; + seasonItem.style.padding = '12px'; + seasonItem.style.margin = '8px 0'; seasonItem.style.border = '1px solid #ddd'; - seasonItem.style.borderRadius = '4px'; + seasonItem.style.borderRadius = '6px'; seasonItem.style.cursor = 'pointer'; seasonItem.style.backgroundColor = '#f8f9fa'; + seasonItem.style.display = 'flex'; + seasonItem.style.justifyContent = 'space-between'; + seasonItem.style.alignItems = 'center'; - // Format season display + // Format season display with episode count let seasonDisplay = `Season ${season.number}`; - if (season.name && season.name !== `Season ${season.number}`) { - seasonDisplay += ` - ${season.name}`; - } - if (season.type) { + if (season.type && season.type !== 'Unknown') { seasonDisplay += ` (${season.type})`; } - if (season.episodeCount) { - seasonDisplay += ` (${season.episodeCount} episodes)`; - } - seasonItem.textContent = seasonDisplay; + const episodeCount = season.episodeCount || 0; + + seasonItem.innerHTML = ` + ${seasonDisplay} + + ${episodeCount} episodes + + `; // Add click event to fetch episodes seasonItem.addEventListener('click', () => { @@ -329,6 +333,9 @@ function displaySeasons(seasons) { // Fetch and display episodes for a season async function fetchAndDisplayEpisodes(showId, seasonNumber) { try { + // Show loading message while fetching + document.getElementById('seasons-container').innerHTML = '
Loading episodes...
'; + const result = await ipcRenderer.invoke('get-season-episodes', showId, seasonNumber); if (result.success && result.data) { @@ -340,11 +347,17 @@ async function fetchAndDisplayEpisodes(showId, seasonNumber) { } } catch (error) { console.error('Error fetching episodes:', error); - document.getElementById('seasons-container').innerHTML = 'Error loading episodes: ' + error.message + '
'; + // Show a more user-friendly error message + document.getElementById('seasons-container').innerHTML = ` +Error loading episodes: ${error.message || 'Failed to load episodes'}
+Note: This may be due to API limitations with the TVDB v4 API.
+