diff --git a/main.js b/main.js index 09d25fb..11fe32d 100644 --- a/main.js +++ b/main.js @@ -184,6 +184,163 @@ ipcMain.handle('search-tvdb', async (event, query) => { } }); +// IPC handler for getting show details including seasons +ipcMain.handle('get-show-details', async (event, showId) => { + try { + // Import required modules + const axios = require('axios'); + + // Get API key from environment + const apiKey = process.env.TVDB_API_KEY || process.env.API_KEY; + + if (!apiKey) { + throw new Error('TVDB_API_KEY not found in environment variables'); + } + + // For TVDB v4 API, we need to first get an authentication token + let token = null; + + // Try to get token from cache or login + try { + const loginResponse = await axios.post('https://api4.thetvdb.com/v4/login', { + apikey: apiKey + }, { + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + token = loginResponse.data.data.token; + } catch (loginError) { + console.error('TVDB Login Error:', loginError.message); + throw new Error('Failed to authenticate with TVDB API: ' + loginError.message); + } + + // Make API request to get show extended details including seasons + const response = await axios.get(`https://api4.thetvdb.com/v4/series/${showId}/extended`, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + if (response.data && response.data.data) { + const showData = response.data.data; + + // Process seasons - TVDB v4 API returns seasons in the show data + const seasons = (showData.seasons || []).map(season => ({ + id: season.id, + name: season.name, + number: season.number, + type: season.type?.name, + episodeCount: season.episodeCount + })); + + return { + success: true, + data: { + id: showData.id, + name: showData.name, + status: showData.status?.name, + 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); + console.error('Error data:', error.response?.data); + + return { success: false, error: 'Failed to fetch show details from TVDB API: ' + error.message }; + } +}); + +// IPC handler for getting season episodes +ipcMain.handle('get-season-episodes', async (event, showId, seasonNumber) => { + try { + // Import required modules + const axios = require('axios'); + + // Get API key from environment + const apiKey = process.env.TVDB_API_KEY || process.env.API_KEY; + + if (!apiKey) { + throw new Error('TVDB_API_KEY not found in environment variables'); + } + + // For TVDB v4 API, we need to first get an authentication token + let token = null; + + // Try to get token from cache or login + try { + const loginResponse = await axios.post('https://api4.thetvdb.com/v4/login', { + apikey: apiKey + }, { + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + token = loginResponse.data.data.token; + } catch (loginError) { + console.error('TVDB Login Error:', loginError.message); + 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'); + } + } 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 }; + } +}); + // Test function to verify API connectivity ipcMain.handle('test-tvdb-api', async () => { try { diff --git a/renderer.js b/renderer.js index 66ae0cc..d7f038d 100644 --- a/renderer.js +++ b/renderer.js @@ -7,10 +7,14 @@ const selectedDirEl = document.getElementById('selected-dir'); const searchInput = document.getElementById('search-input'); const searchResultsEl = document.getElementById('search-results'); const fileListEl = document.getElementById('file-list'); +const showDetailsEl = document.getElementById('show-details'); // Current state let currentDirectory = null; let currentFiles = []; +let currentShow = null; +let currentSeasons = []; +let currentEpisodes = []; // Event Listeners selectDirBtn.addEventListener('click', selectDirectory); @@ -241,10 +245,144 @@ function displaySearchResults(results) { } // Select a show and display more details -function selectShow(show) { +async function selectShow(show) { console.log('Selected show:', show); - // In a real implementation, this would fetch show details and display seasons - alert(`Selected show: ${show.seriesName}\nStatus: ${show.status || 'N/A'}\nFirst Aired: ${show.firstAired || 'N/A'}`); + currentShow = show; + + // Display show title + document.getElementById('show-title').textContent = show.seriesName || show.name; + + // Fetch show details including seasons + try { + const result = await ipcRenderer.invoke('get-show-details', show.id); + + if (result.success && result.data) { + const showData = result.data; + currentSeasons = showData.seasons || []; + + // Display seasons + displaySeasons(currentSeasons); + } else { + console.error('Failed to fetch show details:', result.error); + document.getElementById('seasons-container').innerHTML = '

Error loading seasons: ' + (result.error || 'Unknown error') + '

'; + } + } catch (error) { + console.error('Error fetching show details:', error); + document.getElementById('seasons-container').innerHTML = '

Error loading seasons: ' + error.message + '

'; + } +} + +// Display seasons +function displaySeasons(seasons) { + const seasonsContainer = document.getElementById('seasons-container'); + seasonsContainer.innerHTML = ''; + + if (!seasons || seasons.length === 0) { + seasonsContainer.innerHTML = '

No seasons available.

'; + return; + } + + // Create a container for seasons + const seasonsList = document.createElement('div'); + seasonsList.style.marginTop = '10px'; + + seasons.forEach(season => { + const seasonItem = document.createElement('div'); + seasonItem.className = 'season-item'; + seasonItem.style.padding = '8px'; + seasonItem.style.margin = '5px 0'; + seasonItem.style.border = '1px solid #ddd'; + seasonItem.style.borderRadius = '4px'; + seasonItem.style.cursor = 'pointer'; + seasonItem.style.backgroundColor = '#f8f9fa'; + + // Format season display + let seasonDisplay = `Season ${season.number}`; + if (season.name && season.name !== `Season ${season.number}`) { + seasonDisplay += ` - ${season.name}`; + } + if (season.type) { + seasonDisplay += ` (${season.type})`; + } + if (season.episodeCount) { + seasonDisplay += ` (${season.episodeCount} episodes)`; + } + + seasonItem.textContent = seasonDisplay; + + // Add click event to fetch episodes + seasonItem.addEventListener('click', () => { + fetchAndDisplayEpisodes(currentShow.id, season.number); + }); + + seasonsList.appendChild(seasonItem); + }); + + seasonsContainer.appendChild(seasonsList); +} + +// Fetch and display episodes for a season +async function fetchAndDisplayEpisodes(showId, seasonNumber) { + try { + const result = await ipcRenderer.invoke('get-season-episodes', showId, seasonNumber); + + if (result.success && result.data) { + const episodes = result.data.episodes || []; + displayEpisodes(episodes); + } else { + console.error('Failed to fetch episodes:', result.error); + document.getElementById('seasons-container').innerHTML = '

Error loading episodes: ' + (result.error || 'Unknown error') + '

'; + } + } catch (error) { + console.error('Error fetching episodes:', error); + document.getElementById('seasons-container').innerHTML = '

Error loading episodes: ' + error.message + '

'; + } +} + +// Display episodes +function displayEpisodes(episodes) { + const seasonsContainer = document.getElementById('seasons-container'); + seasonsContainer.innerHTML = ''; + + if (!episodes || episodes.length === 0) { + seasonsContainer.innerHTML = '

No episodes available.

'; + return; + } + + // Create a container for episodes + const episodesList = document.createElement('div'); + episodesList.style.marginTop = '10px'; + + episodes.forEach(episode => { + const episodeItem = document.createElement('div'); + episodeItem.className = 'episode-item'; + episodeItem.style.padding = '8px'; + episodeItem.style.margin = '5px 0'; + episodeItem.style.border = '1px solid #ddd'; + episodeItem.style.borderRadius = '4px'; + episodeItem.style.cursor = 'pointer'; + episodeItem.style.backgroundColor = '#fff'; + + // Format episode display + let episodeDisplay = `Episode ${episode.number}: ${episode.name || 'Untitled'}`; + if (episode.aired) { + episodeDisplay += ` (${episode.aired})`; + } + if (episode.runtime) { + episodeDisplay += ` (${episode.runtime} min)`; + } + + episodeItem.textContent = episodeDisplay; + + // Add click event (could show more details) + episodeItem.addEventListener('click', () => { + alert(`Episode: ${episode.name || 'Untitled'}\nRuntime: ${episode.runtime || 'N/A'} min\nAired: ${episode.aired || 'N/A'}`); + }); + + episodesList.appendChild(episodeItem); + }); + + seasonsContainer.appendChild(episodesList); } // Debounce function for search input