diff --git a/ios/Nuvio.xcodeproj/project.pbxproj b/ios/Nuvio.xcodeproj/project.pbxproj index 084418f..a1ef76a 100644 --- a/ios/Nuvio.xcodeproj/project.pbxproj +++ b/ios/Nuvio.xcodeproj/project.pbxproj @@ -461,7 +461,7 @@ ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app; - PRODUCT_NAME = Nuvio; + PRODUCT_NAME = "Nuvio"; SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -493,7 +493,7 @@ ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app; - PRODUCT_NAME = Nuvio; + PRODUCT_NAME = "Nuvio"; SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/ios/Nuvio/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png b/ios/Nuvio/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png index e9a189f..4e9f344 100644 Binary files a/ios/Nuvio/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png and b/ios/Nuvio/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png differ diff --git a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image.png b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image.png index efcdf22..83330c3 100644 Binary files a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image.png and b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image.png differ diff --git a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png index efcdf22..83330c3 100644 Binary files a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png and b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png differ diff --git a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png index efcdf22..83330c3 100644 Binary files a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png and b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png differ diff --git a/src/hooks/useCalendarData.ts b/src/hooks/useCalendarData.ts index a209146..5797ea0 100644 --- a/src/hooks/useCalendarData.ts +++ b/src/hooks/useCalendarData.ts @@ -55,7 +55,7 @@ export const useCalendarData = (): UseCalendarDataReturn => { try { // Check memory pressure and cleanup if needed memoryManager.checkMemoryPressure(); - + if (!forceRefresh) { const cachedData = await robustCalendarCache.getCachedCalendarData( libraryItems, @@ -65,7 +65,7 @@ export const useCalendarData = (): UseCalendarDataReturn => { watched: watchedShows, } ); - + if (cachedData) { setCalendarData(cachedData); setLoading(false); @@ -156,11 +156,11 @@ export const useCalendarData = (): UseCalendarDataReturn => { allSeries, async (series: StreamingContent, index: number) => { try { - // Use the new memory-efficient method to fetch only upcoming episodes + // Use the new memory-efficient method to fetch upcoming and recent episodes const episodeData = await stremioService.getUpcomingEpisodes(series.type, series.id, { - daysBack: 14, // 2 weeks back - daysAhead: 28, // 4 weeks ahead - maxEpisodes: 25, // Limit episodes per series + daysBack: 90, // 3 months back for recently released episodes + daysAhead: 60, // 2 months ahead for upcoming episodes + maxEpisodes: 50, // Increased limit to get more episodes per series }); if (episodeData && episodeData.episodes.length > 0) { @@ -191,7 +191,7 @@ export const useCalendarData = (): UseCalendarDataReturn => { // Transform episodes with memory-efficient processing const transformedEpisodes = episodeData.episodes.map(video => { const tmdbEpisode = tmdbEpisodes[`${video.season}:${video.episode}`] || {}; - return { + const episode = { id: video.id, seriesId: series.id, title: tmdbEpisode.name || video.title || `Episode ${video.episode}`, @@ -205,6 +205,15 @@ export const useCalendarData = (): UseCalendarDataReturn => { still_path: tmdbEpisode.still_path || null, season_poster_path: tmdbEpisode.season_poster_path || null }; + + // Debug log for episodes + if (episode.releaseDate) { + logger.log(`[CalendarData] Episode with date: ${episode.seriesName} - ${episode.title} (${episode.releaseDate})`); + } else { + logger.log(`[CalendarData] Episode without date: ${episode.seriesName} - ${episode.title}`); + } + + return episode; }); // Clear references to help garbage collection @@ -257,10 +266,17 @@ export const useCalendarData = (): UseCalendarDataReturn => { // Process results and separate episodes from no-episode series for (const result of processedSeries) { + if (!result) { + logger.error(`[CalendarData] Null/undefined result in processedSeries`); + continue; + } + if (result.type === 'episodes' && Array.isArray(result.data)) { allEpisodes.push(...result.data); - } else if (result.type === 'no-episodes') { + } else if (result.type === 'no-episodes' && result.data) { seriesWithoutEpisodes.push(result.data as CalendarEpisode); + } else { + logger.warn(`[CalendarData] Unexpected result type or missing data:`, result); } } @@ -271,35 +287,111 @@ export const useCalendarData = (): UseCalendarDataReturn => { allEpisodes = memoryManager.limitArraySize(allEpisodes, 500); seriesWithoutEpisodes = memoryManager.limitArraySize(seriesWithoutEpisodes, 100); - // Sort episodes by release date - allEpisodes.sort((a, b) => new Date(a.releaseDate).getTime() - new Date(b.releaseDate).getTime()); - - // Use memory-efficient filtering + // Sort episodes by release date with error handling + allEpisodes.sort((a, b) => { + try { + const dateA = new Date(a.releaseDate).getTime(); + const dateB = new Date(b.releaseDate).getTime(); + return dateA - dateB; + } catch (error) { + logger.warn(`[CalendarData] Error sorting episodes: ${a.releaseDate} vs ${b.releaseDate}`, error); + return 0; // Keep original order if sorting fails + } + }); + + logger.log(`[CalendarData] Total episodes fetched: ${allEpisodes.length}`); + + // Use memory-efficient filtering with error handling const thisWeekEpisodes = await memoryManager.filterLargeArray( - allEpisodes, - ep => isThisWeek(parseISO(ep.releaseDate)) + allEpisodes, + ep => { + try { + if (!ep.releaseDate) return false; + const parsed = parseISO(ep.releaseDate); + return isThisWeek(parsed) && isAfter(parsed, new Date()); + } catch (error) { + logger.warn(`[CalendarData] Error parsing date for this week filtering: ${ep.releaseDate}`, error); + return false; + } + } ); - + const upcomingEpisodes = await memoryManager.filterLargeArray( - allEpisodes, - ep => isAfter(parseISO(ep.releaseDate), new Date()) && !isThisWeek(parseISO(ep.releaseDate)) + allEpisodes, + ep => { + try { + if (!ep.releaseDate) return false; + const parsed = parseISO(ep.releaseDate); + return isAfter(parsed, new Date()) && !isThisWeek(parsed); + } catch (error) { + logger.warn(`[CalendarData] Error parsing date for upcoming filtering: ${ep.releaseDate}`, error); + return false; + } + } ); - + const recentEpisodes = await memoryManager.filterLargeArray( - allEpisodes, - ep => isBefore(parseISO(ep.releaseDate), new Date()) && !isThisWeek(parseISO(ep.releaseDate)) + allEpisodes, + ep => { + try { + if (!ep.releaseDate) return false; + const parsed = parseISO(ep.releaseDate); + return isBefore(parsed, new Date()); + } catch (error) { + logger.warn(`[CalendarData] Error parsing date for recent filtering: ${ep.releaseDate}`, error); + return false; + } + } ); - + + logger.log(`[CalendarData] Episode categorization: This Week: ${thisWeekEpisodes.length}, Upcoming: ${upcomingEpisodes.length}, Recently Released: ${recentEpisodes.length}, Series without episodes: ${seriesWithoutEpisodes.length}`); + + // Debug: Show some example episodes from each category + if (thisWeekEpisodes && thisWeekEpisodes.length > 0) { + logger.log(`[CalendarData] This Week examples:`, thisWeekEpisodes.slice(0, 3).map(ep => ({ + title: ep.title, + date: ep.releaseDate, + series: ep.seriesName + }))); + } + if (recentEpisodes && recentEpisodes.length > 0) { + logger.log(`[CalendarData] Recently Released examples:`, recentEpisodes.slice(0, 3).map(ep => ({ + title: ep.title, + date: ep.releaseDate, + series: ep.seriesName + }))); + } + const sections: CalendarSection[] = []; - if (thisWeekEpisodes.length > 0) sections.push({ title: 'This Week', data: thisWeekEpisodes }); - if (upcomingEpisodes.length > 0) sections.push({ title: 'Upcoming', data: upcomingEpisodes }); - if (recentEpisodes.length > 0) sections.push({ title: 'Recently Released', data: recentEpisodes }); - if (seriesWithoutEpisodes.length > 0) sections.push({ title: 'Series with No Scheduled Episodes', data: seriesWithoutEpisodes }); - + if (thisWeekEpisodes.length > 0) { + sections.push({ title: 'This Week', data: thisWeekEpisodes }); + logger.log(`[CalendarData] Added 'This Week' section with ${thisWeekEpisodes.length} episodes`); + } + if (upcomingEpisodes.length > 0) { + sections.push({ title: 'Upcoming', data: upcomingEpisodes }); + logger.log(`[CalendarData] Added 'Upcoming' section with ${upcomingEpisodes.length} episodes`); + } + if (recentEpisodes.length > 0) { + sections.push({ title: 'Recently Released', data: recentEpisodes }); + logger.log(`[CalendarData] Added 'Recently Released' section with ${recentEpisodes.length} episodes`); + } + if (seriesWithoutEpisodes.length > 0) { + sections.push({ title: 'Series with No Scheduled Episodes', data: seriesWithoutEpisodes }); + logger.log(`[CalendarData] Added 'Series with No Scheduled Episodes' section with ${seriesWithoutEpisodes.length} episodes`); + } + + // Log section details before setting + logger.log(`[CalendarData] About to set calendarData with ${sections.length} sections:`); + sections.forEach((section, index) => { + logger.log(` Section ${index}: "${section.title}" with ${section.data?.length || 0} episodes`); + }); + setCalendarData(sections); - + // Clear large arrays to help garbage collection - memoryManager.clearObjects(allEpisodes, thisWeekEpisodes, upcomingEpisodes, recentEpisodes); + // Note: Don't clear the arrays that are referenced in sections (thisWeekEpisodes, upcomingEpisodes, recentEpisodes, seriesWithoutEpisodes) + // as they would empty the section data + memoryManager.clearObjects(allEpisodes); await robustCalendarCache.setCachedCalendarData( sections, diff --git a/src/hooks/useFeaturedContent.ts b/src/hooks/useFeaturedContent.ts index ca2ecdf..0f04e60 100644 --- a/src/hooks/useFeaturedContent.ts +++ b/src/hooks/useFeaturedContent.ts @@ -59,25 +59,16 @@ export function useFeaturedContent() { const loadFeaturedContent = useCallback(async (forceRefresh = false) => { const t0 = Date.now(); - logger.info('[useFeaturedContent] load:start', { forceRefresh, contentSource: 'catalogs', selectedCatalogsCount: (selectedCatalogs || []).length }); // Check if we should use cached data (disabled if DISABLE_CACHE) const now = Date.now(); const cacheAge = now - persistentStore.lastFetchTime; - logger.debug('[useFeaturedContent] cache:status', { - disabled: DISABLE_CACHE, - hasFeatured: Boolean(persistentStore.featuredContent), - allCount: persistentStore.allFeaturedContent?.length || 0, - cacheAgeMs: cacheAge, - timeoutMs: CACHE_TIMEOUT, - }); if (!DISABLE_CACHE) { if (!forceRefresh && persistentStore.featuredContent && persistentStore.allFeaturedContent.length > 0 && cacheAge < CACHE_TIMEOUT) { // Use cached data - logger.info('[useFeaturedContent] cache:use', { duration: `${Date.now() - t0}ms` }); setFeaturedContent(persistentStore.featuredContent); setAllFeaturedContent(persistentStore.allFeaturedContent); setLoading(false); @@ -86,7 +77,6 @@ export function useFeaturedContent() { } } - logger.info('[useFeaturedContent] fetch:start', { source: 'catalogs' }); setLoading(true); cleanup(); abortControllerRef.current = new AbortController(); @@ -99,7 +89,6 @@ export function useFeaturedContent() { // Load from installed catalogs const tCats = Date.now(); const catalogs = await catalogService.getHomeCatalogs(); - logger.info('[useFeaturedContent] catalogs:list', { count: catalogs?.length || 0, duration: `${Date.now() - tCats}ms` }); if (signal.aborted) return; @@ -114,7 +103,6 @@ export function useFeaturedContent() { return selectedCatalogs.includes(catalogId); }) : catalogs; // Use all catalogs if none specifically selected - logger.debug('[useFeaturedContent] catalogs:filtered', { filteredCount: filteredCatalogs.length, selectedCount: selectedCatalogs?.length || 0 }); // Flatten all catalog items into a single array, filter out items without posters const tFlat = Date.now(); @@ -124,7 +112,6 @@ export function useFeaturedContent() { // Remove duplicates based on ID index === self.findIndex(t => t.id === item.id) ); - logger.info('[useFeaturedContent] catalogs:items', { total: allItems.length, duration: `${Date.now() - tFlat}ms` }); // Sort by popular, newest, etc. (possibly enhanced later) and take first 10 const topItems = allItems.sort(() => Math.random() - 0.5).slice(0, 10); @@ -149,10 +136,8 @@ export function useFeaturedContent() { // If enrichment is disabled, use addon logo if available if (!settings.enrichMetadataWithTMDB) { if (base.logo && !isTmdbUrl(base.logo)) { - logger.debug('[useFeaturedContent] enrichment disabled, using addon logo', { name: item.name, logo: base.logo }); return base; } - logger.debug('[useFeaturedContent] enrichment disabled, no addon logo available', { name: item.name }); return { ...base, logo: undefined }; } @@ -172,16 +157,13 @@ export function useFeaturedContent() { if (!tmdbId && !imdbId) return base; // Try TMDB if we have a TMDB id if (tmdbId) { - logger.debug('[useFeaturedContent] logo:try:tmdb', { name: item.name, id: item.id, tmdbId, lang: preferredLanguage }); const logoUrl = await tmdbService.getContentLogo(item.type === 'series' ? 'tv' : 'movie', tmdbId as string, preferredLanguage); if (logoUrl) { - logger.debug('[useFeaturedContent] logo:tmdb:ok', { name: item.name, id: item.id, url: logoUrl, lang: preferredLanguage }); return { ...base, logo: logoUrl }; } } return base; } catch (error) { - logger.error('[useFeaturedContent] logo:error', { name: item.name, id: item.id, error: String(error) }); return base; } }; @@ -197,7 +179,6 @@ export function useFeaturedContent() { logoSource: c.logo ? (isTmdbUrl(String(c.logo)) ? 'tmdb' : 'addon') : 'none', logo: c.logo || undefined, })); - logger.info('[useFeaturedContent] catalogs:logos:details (enrich=true)', { items: details }); } catch {} } else { // When enrichment is disabled, prefer addon-provided logos; if missing, fetch basic meta to pull logo (like HeroSection) @@ -219,18 +200,15 @@ export function useFeaturedContent() { // Attempt to fill missing logos from addon meta details for a limited subset const candidates = baseItems.filter(i => !i.logo).slice(0, 10); - logger.debug('[useFeaturedContent] catalogs:no-enrich:missing-logos', { count: candidates.length }); try { const filled = await Promise.allSettled(candidates.map(async (item) => { try { const meta = await catalogService.getBasicContentDetails(item.type, item.id); if (meta?.logo) { - logger.debug('[useFeaturedContent] catalogs:no-enrich:filled-logo', { id: item.id, name: item.name, logo: meta.logo }); return { id: item.id, logo: meta.logo } as { id: string; logo: string }; } } catch (e) { - logger.warn('[useFeaturedContent] catalogs:no-enrich:fill-failed', { id: item.id, error: String(e) }); } return { id: item.id, logo: undefined as any }; })); @@ -257,7 +235,6 @@ export function useFeaturedContent() { logoSource: c.logo ? (isTmdbUrl(String(c.logo)) ? 'tmdb' : 'addon') : 'none', logo: c.logo || undefined, })); - logger.info('[useFeaturedContent] catalogs:logos:details (no-enrich)', { items: details }); } catch {} } } @@ -267,7 +244,6 @@ export function useFeaturedContent() { // Safety guard: if nothing came back within a reasonable time, stop loading if (!formattedContent || formattedContent.length === 0) { - logger.warn('[useFeaturedContent] results:empty'); // Fall back to any cached featured item so UI can render something const cachedJson = await AsyncStorage.getItem(STORAGE_KEY).catch(() => null); if (cachedJson) { @@ -277,7 +253,6 @@ export function useFeaturedContent() { formattedContent = Array.isArray(parsed.allFeaturedContent) && parsed.allFeaturedContent.length > 0 ? parsed.allFeaturedContent : [parsed.featuredContent]; - logger.info('[useFeaturedContent] fallback:storage', { count: formattedContent.length }); } } catch {} } @@ -295,12 +270,6 @@ export function useFeaturedContent() { if (formattedContent.length > 0) { persistentStore.featuredContent = formattedContent[0]; setFeaturedContent(formattedContent[0]); - logger.info('[useFeaturedContent] setting featuredContent', { - id: formattedContent[0].id, - name: formattedContent[0].name, - hasLogo: Boolean(formattedContent[0].logo), - logo: formattedContent[0].logo - }); currentIndexRef.current = 0; // Persist cache for fast startup (skipped when cache disabled) if (!DISABLE_CACHE) { @@ -313,7 +282,6 @@ export function useFeaturedContent() { allFeaturedContent: formattedContent, }) ); - logger.debug('[useFeaturedContent] cache:written', { firstId: formattedContent[0]?.id }); } catch {} } } else { @@ -326,16 +294,13 @@ export function useFeaturedContent() { } } catch (error) { if (signal.aborted) { - logger.info('[useFeaturedContent] fetch:aborted'); } else { - logger.error('[useFeaturedContent] fetch:error', { error: String(error) }); } setFeaturedContent(null); setAllFeaturedContent([]); } finally { if (!signal.aborted) { setLoading(false); - logger.info('[useFeaturedContent] load:done', { duration: `${Date.now() - t0}ms` }); } } }, [cleanup, genreMap, loadingGenres, selectedCatalogs]); @@ -344,7 +309,6 @@ export function useFeaturedContent() { useEffect(() => { if (DISABLE_CACHE) { // Skip hydration entirely - logger.debug('[useFeaturedContent] hydrate:skipped'); return; } let cancelled = false; @@ -364,7 +328,6 @@ export function useFeaturedContent() { setFeaturedContent(parsed.featuredContent); setAllFeaturedContent(persistentStore.allFeaturedContent); setLoading(false); - logger.info('[useFeaturedContent] hydrate:storage', { allCount: persistentStore.allFeaturedContent.length }); } } } catch {} @@ -392,7 +355,6 @@ export function useFeaturedContent() { // Force refresh if settings changed during app restart, but only if we have content if (settingsChanged && persistentStore.featuredContent) { - logger.info('[useFeaturedContent] settings:changed', { selectedCount: settings.selectedHeroCatalogs?.length || 0 }); loadFeaturedContent(true); } }, [settings, loadFeaturedContent]); @@ -410,11 +372,6 @@ export function useFeaturedContent() { const tmdbLangChanged = persistentStore.lastSettings.tmdbLanguagePreference !== nextTmdbLang; if (catalogsChanged || logoPrefChanged || tmdbLangChanged) { - logger.info('[useFeaturedContent] event:settings-changed:immediate-refresh', { - catalogsChanged, - logoPrefChanged, - tmdbLangChanged - }); // Update internal state immediately so dependent effects are in sync setSelectedCatalogs(nextSelected); diff --git a/src/screens/CalendarScreen.tsx b/src/screens/CalendarScreen.tsx index e2eb31a..a3318d3 100644 --- a/src/screens/CalendarScreen.tsx +++ b/src/screens/CalendarScreen.tsx @@ -232,6 +232,20 @@ const CalendarScreen = () => { // Log when rendering with relevant state info logger.log(`[Calendar] Rendering: loading=${loading}, calendarData sections=${calendarData.length}, allEpisodes=${allEpisodes.length}`); + + // Log section details + if (calendarData.length > 0) { + calendarData.forEach((section, index) => { + logger.log(`[Calendar] Section ${index}: "${section.title}" with ${section.data.length} episodes`); + if (section.data && section.data.length > 0) { + logger.log(`[Calendar] First episode in "${section.title}": ${section.data[0].seriesName} - ${section.data[0].title} (${section.data[0].releaseDate})`); + } else { + logger.log(`[Calendar] Section "${section.title}" has empty or undefined data array`); + } + }); + } else { + logger.log(`[Calendar] No calendarData sections available`); + } // Handle date selection from calendar const handleDateSelect = useCallback((date: Date) => { diff --git a/src/services/catalogService.ts b/src/services/catalogService.ts index d2381e5..09236b3 100644 --- a/src/services/catalogService.ts +++ b/src/services/catalogService.ts @@ -714,14 +714,6 @@ class CatalogService { if (!logoUrl || logoUrl.trim() === '' || logoUrl === 'null' || logoUrl === 'undefined') { logoUrl = undefined; } - try { - logger.debug('[CatalogService] convertMetaToStreamingContent:logo', { - id: meta.id, - name: meta.name, - hasLogo: Boolean(logoUrl), - logo: logoUrl || undefined, - }); - } catch {} return { id: meta.id, diff --git a/src/services/robustCalendarCache.ts b/src/services/robustCalendarCache.ts index 9ca3296..7e9a4de 100644 --- a/src/services/robustCalendarCache.ts +++ b/src/services/robustCalendarCache.ts @@ -16,7 +16,7 @@ interface TraktCollections { } const THIS_WEEK_CACHE_KEY = 'this_week_episodes_cache'; -const CALENDAR_CACHE_KEY = 'calendar_data_cache'; +const CALENDAR_CACHE_KEY = 'calendar_data_cache_v2'; const CACHE_DURATION_MS = 30 * 60 * 1000; // 30 minutes (increased to reduce API calls) const ERROR_CACHE_DURATION_MS = 5 * 60 * 1000; // 5 minutes for error recovery diff --git a/src/services/stremioService.ts b/src/services/stremioService.ts index 225d428..3ad571a 100644 --- a/src/services/stremioService.ts +++ b/src/services/stremioService.ts @@ -752,13 +752,11 @@ class StremioService { const urlPathStyle = `${baseUrl}/catalog/${type}/${encodedId}/skip=${pageSkip}.json${queryParams ? `?${queryParams}` : ''}`; // Add filters to path style (append with & or ? based on presence of queryParams) const urlPathWithFilters = urlPathStyle + (urlPathStyle.includes('?') ? filterQuery : (filterQuery ? `?${filterQuery.slice(1)}` : '')); - try { logger.log('[StremioService] getCatalog URL (path-style)', { url: urlPathWithFilters, page, pageSize: this.DEFAULT_PAGE_SIZE, type, id, filters, manifest: manifest.id }); } catch {} // Candidate 2: Query-style skip URL: /catalog/{type}/{id}.json?skip={N}&limit={PAGE_SIZE} let urlQueryStyle = `${baseUrl}/catalog/${type}/${encodedId}.json?skip=${pageSkip}&limit=${this.DEFAULT_PAGE_SIZE}`; if (queryParams) urlQueryStyle += `&${queryParams}`; urlQueryStyle += filterQuery; - try { logger.log('[StremioService] getCatalog URL (query-style)', { url: urlQueryStyle, page, pageSize: this.DEFAULT_PAGE_SIZE, type, id, filters, manifest: manifest.id }); } catch {} // Try path-style first, then fallback to query-style let response; @@ -777,7 +775,6 @@ class StremioService { try { const key = `${manifest.id}|${type}|${id}`; if (typeof hasMore === 'boolean') this.catalogHasMore.set(key, hasMore); - logger.log('[StremioService] getCatalog response meta', { hasMore, count: Array.isArray(response.data.metas) ? response.data.metas.length : 0 }); } catch {} if (response.data.metas && Array.isArray(response.data.metas)) { return response.data.metas; @@ -796,23 +793,18 @@ class StremioService { } async getMetaDetails(type: string, id: string, preferredAddonId?: string): Promise { - console.log(`🔍 [StremioService] getMetaDetails called:`, { type, id, preferredAddonId }); try { // Validate content ID first const isValidId = await this.isValidContentId(type, id); - console.log(`🔍 [StremioService] Content ID validation:`, { type, id, isValidId }); if (!isValidId) { - console.log(`🔍 [StremioService] Invalid content ID, returning null`); return null; } const addons = this.getInstalledAddons(); - console.log(`🔍 [StremioService] Found ${addons.length} installed addons`); // If a preferred addon is specified, try it first if (preferredAddonId) { - console.log(`🔍 [StremioService] Preferred addon specified:`, { preferredAddonId }); const preferredAddon = addons.find(addon => addon.id === preferredAddonId); if (preferredAddon && preferredAddon.resources) { @@ -857,49 +849,26 @@ class StremioService { } } - console.log(`🔍 [StremioService] Preferred addon support check:`, { - hasMetaSupport, - supportsIdPrefix, - addonId: preferredAddon.id, - addonName: preferredAddon.name, - hasDeclaredPrefixes: preferredAddon.idPrefixes && preferredAddon.idPrefixes.length > 0 - }); // Only require ID prefix compatibility if the addon has declared specific prefixes const requiresIdPrefix = preferredAddon.idPrefixes && preferredAddon.idPrefixes.length > 0; const isSupported = hasMetaSupport && (!requiresIdPrefix || supportsIdPrefix); if (isSupported) { - console.log(`🔍 [StremioService] Requesting metadata from preferred addon:`, { url }); try { const response = await this.retryRequest(async () => { return await axios.get(url, { timeout: 10000 }); }); - console.log(`🔍 [StremioService] Preferred addon response:`, { - hasData: !!response.data, - hasMeta: !!response.data?.meta, - metaId: response.data?.meta?.id, - metaName: response.data?.meta?.name - }); if (response.data && response.data.meta) { - console.log(`🔍 [StremioService] Successfully got metadata from preferred addon`); return response.data.meta; } else { - console.log(`🔍 [StremioService] Preferred addon returned no metadata`); } } catch (error: any) { - console.log(`🔍 [StremioService] Preferred addon request failed:`, { - errorMessage: error.message, - isAxiosError: error.isAxiosError, - responseStatus: error.response?.status, - responseData: error.response?.data - }); // Continue trying other addons } } else { - console.log(`🔍 [StremioService] Preferred addon doesn't support this content type${requiresIdPrefix ? ' or ID prefix' : ''}`); } } } @@ -910,40 +879,23 @@ class StremioService { 'http://v3-cinemeta.strem.io' ]; - console.log(`🔍 [StremioService] Trying Cinemeta URLs:`, { cinemetaUrls }); for (const baseUrl of cinemetaUrls) { try { const encodedId = encodeURIComponent(id); const url = `${baseUrl}/meta/${type}/${encodedId}.json`; - console.log(`🔍 [StremioService] Requesting from Cinemeta:`, { url }); const response = await this.retryRequest(async () => { return await axios.get(url, { timeout: 10000 }); }); - console.log(`🔍 [StremioService] Cinemeta response:`, { - hasData: !!response.data, - hasMeta: !!response.data?.meta, - metaId: response.data?.meta?.id, - metaName: response.data?.meta?.name - }); if (response.data && response.data.meta) { - console.log(`🔍 [StremioService] Successfully got metadata from Cinemeta`); return response.data.meta; } else { - console.log(`🔍 [StremioService] Cinemeta returned no metadata`); } } catch (error: any) { - console.log(`🔍 [StremioService] Cinemeta request failed:`, { - baseUrl, - errorMessage: error.message, - isAxiosError: error.isAxiosError, - responseStatus: error.response?.status, - responseData: error.response?.data - }); continue; // Try next URL } } @@ -989,20 +941,12 @@ class StremioService { } // Require meta support, but allow any ID if addon doesn't declare specific prefixes - console.log(`🔍 [StremioService] Addon support check:`, { - addonId: addon.id, - addonName: addon.name, - hasMetaSupport, - supportsIdPrefix, - hasDeclaredPrefixes: addon.idPrefixes && addon.idPrefixes.length > 0 - }); // Only require ID prefix compatibility if the addon has declared specific prefixes const requiresIdPrefix = addon.idPrefixes && addon.idPrefixes.length > 0; const isSupported = hasMetaSupport && (!requiresIdPrefix || supportsIdPrefix); if (!isSupported) { - console.log(`🔍 [StremioService] Addon doesn't support this content type${requiresIdPrefix ? ' or ID prefix' : ''}, skipping`); continue; } @@ -1011,52 +955,23 @@ class StremioService { const encodedId = encodeURIComponent(id); const url = queryParams ? `${baseUrl}/meta/${type}/${encodedId}.json?${queryParams}` : `${baseUrl}/meta/${type}/${encodedId}.json`; - console.log(`🔍 [StremioService] Requesting from addon:`, { - addonId: addon.id, - addonName: addon.name, - url - }); const response = await this.retryRequest(async () => { return await axios.get(url, { timeout: 10000 }); }); - console.log(`🔍 [StremioService] Addon response:`, { - addonId: addon.id, - hasData: !!response.data, - hasMeta: !!response.data?.meta, - metaId: response.data?.meta?.id, - metaName: response.data?.meta?.name - }); if (response.data && response.data.meta) { - console.log(`🔍 [StremioService] Successfully got metadata from addon:`, { addonId: addon.id }); return response.data.meta; } else { - console.log(`🔍 [StremioService] Addon returned no metadata:`, { addonId: addon.id }); } } catch (error: any) { - console.log(`🔍 [StremioService] Addon request failed:`, { - addonId: addon.id, - addonName: addon.name, - errorMessage: error.message, - isAxiosError: error.isAxiosError, - responseStatus: error.response?.status, - responseData: error.response?.data - }); continue; // Try next addon } } - console.log(`🔍 [StremioService] No metadata found from any addon`); return null; } catch (error) { - console.log(`🔍 [StremioService] getMetaDetails caught error:`, { - errorMessage: error instanceof Error ? error.message : String(error), - isAxiosError: (error as any)?.isAxiosError, - responseStatus: (error as any)?.response?.status, - responseData: (error as any)?.response?.data - }); logger.error('Error in getMetaDetails:', error); return null; } @@ -1100,15 +1015,24 @@ class StremioService { // Filter episodes to only include those within our date range // This is done immediately after fetching to reduce memory footprint + logger.log(`[StremioService] Filtering ${metadata.videos.length} episodes for ${id}, date range: ${startDate.toISOString()} to ${endDate.toISOString()}`); + const filteredEpisodes = metadata.videos .filter(video => { - if (!video.released) return false; + if (!video.released) { + logger.log(`[StremioService] Episode ${video.id} has no release date`); + return false; + } const releaseDate = new Date(video.released); - return releaseDate >= startDate && releaseDate <= endDate; + const inRange = releaseDate >= startDate && releaseDate <= endDate; + logger.log(`[StremioService] Episode ${video.id}: released=${video.released}, inRange=${inRange}`); + return inRange; }) .sort((a, b) => new Date(a.released).getTime() - new Date(b.released).getTime()) .slice(0, maxEpisodes); // Limit number of episodes to prevent memory overflow + logger.log(`[StremioService] After filtering: ${filteredEpisodes.length} episodes remain`); + return { seriesName: metadata.name, poster: metadata.poster || '',