calender screen fix

This commit is contained in:
tapframe 2025-10-19 12:19:28 +05:30
parent 707ceb711a
commit ef43463b99
11 changed files with 148 additions and 169 deletions

View file

@ -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";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 KiB

After

Width:  |  Height:  |  Size: 122 KiB

View file

@ -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,

View file

@ -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);

View file

@ -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) => {

View file

@ -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,

View file

@ -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

View file

@ -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<MetaDetails | null> {
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 || '',