From bf5f7c60de98a1c9e78794221878627bb244fdaf Mon Sep 17 00:00:00 2001 From: skoruppa Date: Wed, 18 Feb 2026 12:23:19 +0100 Subject: [PATCH] prioritize external meta addons & fix episode ID parsing same as for NuvioTV also fix: correct episode ID parsing for non-season formats (mal:id:episode) --- src/hooks/useMetadata.ts | 159 ++++++++++++++++++++++------- src/hooks/useSettings.ts | 4 + src/i18n/locales/en.json | 2 + src/i18n/locales/pl.json | 2 + src/screens/HomeScreenSettings.tsx | 15 ++- 5 files changed, 142 insertions(+), 40 deletions(-) diff --git a/src/hooks/useMetadata.ts b/src/hooks/useMetadata.ts index 48cbdd72..3773150d 100644 --- a/src/hooks/useMetadata.ts +++ b/src/hooks/useMetadata.ts @@ -734,44 +734,119 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat let contentResult = null; let lastError = null; - // Try with original ID first - try { - console.log('๐Ÿ” [useMetadata] Attempting metadata fetch with original ID:', { type, actualId, addonId }); - const [content, castData] = await Promise.allSettled([ - // Load content with timeout and retry - withRetry(async () => { - console.log('๐Ÿ” [useMetadata] Calling catalogService.getEnhancedContentDetails:', { type, actualId, addonId }); - const result = await withTimeout( - catalogService.getEnhancedContentDetails(type, actualId, addonId), - API_TIMEOUT - ); - // Store the actual ID used (could be IMDB) - if (actualId.startsWith('tt')) { - setImdbId(actualId); - } - console.log('๐Ÿ” [useMetadata] catalogService.getEnhancedContentDetails result:', { - hasResult: Boolean(result), - resultId: result?.id, - resultName: result?.name, - resultType: result?.type - }); - if (__DEV__) logger.log('[loadMetadata] addon metadata fetched', { hasResult: Boolean(result) }); - return result; - }), - // Start loading cast immediately in parallel - loadCast() - ]); + // Check if user prefers external meta addons + const preferExternal = settings.preferExternalMetaAddonDetail; - contentResult = content; - if (content.status === 'fulfilled' && content.value) { - console.log('๐Ÿ” [useMetadata] Successfully got metadata with original ID'); - } else { - console.log('๐Ÿ” [useMetadata] Original ID failed, will try fallback conversion'); - lastError = (content as any)?.reason; + if (preferExternal) { + // Try external meta addons first + try { + console.log('๐Ÿ” [useMetadata] Trying external meta addons first'); + const [content, castData] = await Promise.allSettled([ + withRetry(async () => { + // Get all installed addons + const allAddons = await stremioService.getInstalledAddonsAsync(); + + // Find catalog addon index + const catalogAddonIndex = allAddons.findIndex(addon => addon.id === addonId); + + // Filter for meta addons that are BEFORE catalog addon in priority + const externalMetaAddons = allAddons + .slice(0, catalogAddonIndex >= 0 ? catalogAddonIndex : allAddons.length) + .filter(addon => { + if (!addon.resources || !Array.isArray(addon.resources)) return false; + + return addon.resources.some(resource => { + if (typeof resource === 'string') return resource === 'meta'; + return (resource as any).name === 'meta'; + }); + }); + + // Try each external meta addon in priority order + for (const addon of externalMetaAddons) { + try { + const result = await withTimeout( + stremioService.getMetaDetails(type, actualId, addon.id), + API_TIMEOUT + ); + + if (result) { + console.log('๐Ÿ” [useMetadata] Got metadata from external addon:', addon.name); + if (actualId.startsWith('tt')) { + setImdbId(actualId); + } + return result; + } + } catch (error) { + console.log('๐Ÿ” [useMetadata] External addon failed:', addon.name, error); + continue; + } + } + + // If no external addon worked, fall back to catalog addon + console.log('๐Ÿ” [useMetadata] No external meta addon worked, falling back to catalog addon'); + const result = await withTimeout( + catalogService.getEnhancedContentDetails(type, actualId, addonId), + API_TIMEOUT + ); + if (actualId.startsWith('tt')) { + setImdbId(actualId); + } + return result; + }), + loadCast() + ]); + + contentResult = content; + if (content.status === 'fulfilled' && content.value) { + console.log('๐Ÿ” [useMetadata] Successfully got metadata with external meta addon priority'); + } else { + console.log('๐Ÿ” [useMetadata] External meta addon priority failed, will try fallback'); + lastError = (content as any)?.reason; + } + } catch (error) { + console.log('๐Ÿ” [useMetadata] External meta addon attempt failed:', { error: error instanceof Error ? error.message : String(error) }); + lastError = error; + } + } else { + // Original behavior: try with original ID first + try { + console.log('๐Ÿ” [useMetadata] Attempting metadata fetch with original ID:', { type, actualId, addonId }); + const [content, castData] = await Promise.allSettled([ + // Load content with timeout and retry + withRetry(async () => { + console.log('๐Ÿ” [useMetadata] Calling catalogService.getEnhancedContentDetails:', { type, actualId, addonId }); + const result = await withTimeout( + catalogService.getEnhancedContentDetails(type, actualId, addonId), + API_TIMEOUT + ); + // Store the actual ID used (could be IMDB) + if (actualId.startsWith('tt')) { + setImdbId(actualId); + } + console.log('๐Ÿ” [useMetadata] catalogService.getEnhancedContentDetails result:', { + hasResult: Boolean(result), + resultId: result?.id, + resultName: result?.name, + resultType: result?.type + }); + if (__DEV__) logger.log('[loadMetadata] addon metadata fetched', { hasResult: Boolean(result) }); + return result; + }), + // Start loading cast immediately in parallel + loadCast() + ]); + + contentResult = content; + if (content.status === 'fulfilled' && content.value) { + console.log('๐Ÿ” [useMetadata] Successfully got metadata with original ID'); + } else { + console.log('๐Ÿ” [useMetadata] Original ID failed, will try fallback conversion'); + lastError = (content as any)?.reason; + } + } catch (error) { + console.log('๐Ÿ” [useMetadata] Original ID attempt failed:', { error: error instanceof Error ? error.message : String(error) }); + lastError = error; } - } catch (error) { - console.log('๐Ÿ” [useMetadata] Original ID attempt failed:', { error: error instanceof Error ? error.message : String(error) }); - lastError = error; } // If original TMDB ID failed and enrichment is disabled, try ID conversion as fallback @@ -1831,10 +1906,10 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat seasonNum = parts.pop() || ''; showIdStr = parts.join(':'); } else if (parts.length === 2) { - // Edge case: maybe just id:episode? unlikely but safe fallback - episodeNum = parts[1]; - seasonNum = '1'; // Default + // For IDs like mal:57658:1, this is showId:episode (no season) showIdStr = parts[0]; + episodeNum = parts[1]; + seasonNum = ''; // No season for this format } if (__DEV__) console.log(`๐Ÿ” [loadEpisodeStreams] Parsed ID: show=${showIdStr}, s=${seasonNum}, e=${episodeNum}`); @@ -1912,6 +1987,9 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat // This handles cases where 'tt' is used for a unique episode ID directly if (!seasonNum && !episodeNum) { stremioEpisodeId = episodeId; + } else if (!seasonNum) { + // No season (e.g., mal:57658:1) - use id:episode format + stremioEpisodeId = `${id}:${episodeNum}`; } else { stremioEpisodeId = `${id}:${seasonNum}:${episodeNum}`; } @@ -1923,6 +2001,9 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat if (!seasonNum && !episodeNum) { // Remove 'series:' prefix if present to be safe, though parsing logic above usually handles it stremioEpisodeId = episodeId.replace(/^series:/, ''); + } else if (!seasonNum) { + // No season (e.g., mal:57658:1) - use id:episode format + stremioEpisodeId = `${id}:${episodeNum}`; } else { stremioEpisodeId = `${id}:${seasonNum}:${episodeNum}`; } diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index befff6bb..71093b7e 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -115,6 +115,8 @@ export interface AppSettings { preferredAudioLanguage: string; // Preferred language for audio tracks (ISO 639-1 code) subtitleSourcePreference: 'internal' | 'external' | 'any'; // Prefer internal (embedded), external (addon), or any enableSubtitleAutoSelect: boolean; // Auto-select subtitles based on preferences + // External metadata addon preference + preferExternalMetaAddonDetail: boolean; // Prefer metadata from external meta addons on detail page } export const DEFAULT_SETTINGS: AppSettings = { @@ -203,6 +205,8 @@ export const DEFAULT_SETTINGS: AppSettings = { preferredAudioLanguage: 'en', // Default to English audio subtitleSourcePreference: 'internal', // Prefer internal/embedded subtitles first enableSubtitleAutoSelect: true, // Auto-select subtitles by default + // External metadata addon preference + preferExternalMetaAddonDetail: false, // Disabled by default }; const SETTINGS_STORAGE_KEY = 'app_settings'; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 099177f0..3be593ee 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -983,6 +983,8 @@ "select_catalogs": "Select Catalogs", "all_catalogs": "All catalogs", "selected": "selected", + "prefer_external_meta": "Prefer External Meta Addon", + "prefer_external_meta_desc": "Use external metadata on detail page", "hero_layout": "Hero Layout", "layout_legacy": "Legacy", "layout_carousel": "Carousel", diff --git a/src/i18n/locales/pl.json b/src/i18n/locales/pl.json index 184316c0..0b61952a 100644 --- a/src/i18n/locales/pl.json +++ b/src/i18n/locales/pl.json @@ -983,6 +983,8 @@ "select_catalogs": "Wybierz katalogi", "all_catalogs": "Wszystkie katalogi", "selected": "wybrane", + "prefer_external_meta": "Preferuj zewnฤ™trzny dodatek meta", + "prefer_external_meta_desc": "Uลผywaj zewnฤ™trznych metadanych na stronie szczegรณล‚รณw", "hero_layout": "Ukล‚ad sekcji Hero", "layout_legacy": "Klasyczny", "layout_carousel": "Karuzela", diff --git a/src/screens/HomeScreenSettings.tsx b/src/screens/HomeScreenSettings.tsx index 74822d07..7e563916 100644 --- a/src/screens/HomeScreenSettings.tsx +++ b/src/screens/HomeScreenSettings.tsx @@ -344,9 +344,22 @@ const HomeScreenSettings: React.FC = () => { colors={colors} renderControl={ChevronRight} onPress={() => navigation.navigate('HeroCatalogs')} - isLast={true} /> )} + ( + handleUpdateSetting('preferExternalMetaAddonDetail', value)} + /> + )} + isLast={true} + /> {settings.showHeroSection && (