diff --git a/src/hooks/useMetadata.ts b/src/hooks/useMetadata.ts
index 761272cf..98ee76ca 100644
--- a/src/hooks/useMetadata.ts
+++ b/src/hooks/useMetadata.ts
@@ -870,19 +870,6 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
let hasStreamResource = false;
let supportsIdPrefix = false;
- // Extract ID prefix from the ID
- let idPrefix = id.split(':')[0];
-
- // For IMDb IDs (tt...), extract just the 'tt' prefix
- if (idPrefix.startsWith('tt')) {
- idPrefix = 'tt';
- }
- // For Kitsu IDs, keep the full prefix
- else if (idPrefix === 'kitsu') {
- idPrefix = 'kitsu';
- }
- // For other prefixes, keep as is
-
for (const resource of addon.resources) {
// Check if the current element is a ResourceObject
if (typeof resource === 'object' && resource !== null && 'name' in resource) {
@@ -892,13 +879,13 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
typedResource.types.includes(type)) {
hasStreamResource = true;
- // Check if this addon supports the ID prefix
- if (Array.isArray(typedResource.idPrefixes)) {
- supportsIdPrefix = typedResource.idPrefixes.includes(idPrefix);
- } else {
- // If no idPrefixes specified, assume it supports all prefixes
- supportsIdPrefix = true;
- }
+ // Check if this addon supports the ID prefix generically: any prefix must match start of id
+ if (Array.isArray(typedResource.idPrefixes) && typedResource.idPrefixes.length > 0) {
+ supportsIdPrefix = typedResource.idPrefixes.some((p: string) => id.startsWith(p));
+ } else {
+ // If no idPrefixes specified, assume it supports all prefixes
+ supportsIdPrefix = true;
+ }
break;
}
}
@@ -906,9 +893,9 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
else if (typeof resource === 'string' && resource === 'stream' && addon.types) {
if (Array.isArray(addon.types) && addon.types.includes(type)) {
hasStreamResource = true;
- // For simple string resources, check addon-level idPrefixes
- if (addon.idPrefixes && Array.isArray(addon.idPrefixes)) {
- supportsIdPrefix = addon.idPrefixes.includes(idPrefix);
+ // For simple string resources, check addon-level idPrefixes generically
+ if (addon.idPrefixes && Array.isArray(addon.idPrefixes) && addon.idPrefixes.length > 0) {
+ supportsIdPrefix = addon.idPrefixes.some((p: string) => id.startsWith(p));
} else {
// If no idPrefixes specified, assume it supports all prefixes
supportsIdPrefix = true;
@@ -920,6 +907,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
return hasStreamResource && supportsIdPrefix;
});
+ if (__DEV__) console.log('[useMetadata.loadStreams] Eligible stream addons:', streamAddons.map(a => a.id));
// Initialize scraper statuses for tracking
const initialStatuses: ScraperStatus[] = [];
@@ -1217,7 +1205,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
// Re-run series data loading when metadata updates with videos
useEffect(() => {
- if (metadata && type === 'series' && metadata.videos && metadata.videos.length > 0) {
+ if (metadata && metadata.videos && metadata.videos.length > 0) {
logger.log(`🎬 Metadata updated with ${metadata.videos.length} episodes, reloading series data`);
loadSeriesData().catch((error) => { if (__DEV__) console.error(error); });
}
diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx
index a23dcf2b..38d6325c 100644
--- a/src/screens/MetadataScreen.tsx
+++ b/src/screens/MetadataScreen.tsx
@@ -108,7 +108,7 @@ const MetadataScreen: React.FC = () => {
} = useMetadata({ id, type, addonId });
// Optimized hooks with memoization and conditional loading
- const watchProgressData = useWatchProgress(id, type as 'movie' | 'series', episodeId, episodes);
+ const watchProgressData = useWatchProgress(id, Object.keys(groupedEpisodes).length > 0 ? 'series' : type as 'movie' | 'series', episodeId, episodes);
const assetData = useMetadataAssets(metadata, id, type, imdbId, settings, setMetadata);
const animations = useMetadataAnimations(safeAreaTop, watchProgressData.watchProgress);
@@ -277,9 +277,9 @@ const MetadataScreen: React.FC = () => {
item.type === 'movie' &&
item.movie?.ids.imdb === id.replace('tt', '')
);
- } else if (type === 'series') {
- relevantProgress = allProgress.filter(item =>
- item.type === 'episode' &&
+ } else if (Object.keys(groupedEpisodes).length > 0) {
+ relevantProgress = allProgress.filter(item =>
+ item.type === 'episode' &&
item.show?.ids.imdb === id.replace('tt', '')
);
}
@@ -290,7 +290,7 @@ const MetadataScreen: React.FC = () => {
if (__DEV__) console.log(`[MetadataScreen] Found ${relevantProgress.length} Trakt progress items for ${type}`);
// Find most recent progress if multiple episodes
- if (type === 'series' && relevantProgress.length > 1) {
+ if (Object.keys(groupedEpisodes).length > 0 && relevantProgress.length > 1) {
const mostRecent = relevantProgress.sort((a, b) =>
new Date(b.paused_at).getTime() - new Date(a.paused_at).getTime()
)[0];
@@ -411,7 +411,7 @@ const MetadataScreen: React.FC = () => {
return ep.stremioId || `${id}:${ep.season_number}:${ep.episode_number}`;
};
- if (type === 'series') {
+ if (Object.keys(groupedEpisodes).length > 0) {
// Determine if current episode is finished
let progressPercent = 0;
if (watchProgress && watchProgress.duration > 0) {
@@ -581,7 +581,7 @@ const MetadataScreen: React.FC = () => {
// Show loading screen if metadata is not yet available
if (loading || !isContentReady) {
- return ;
+ return 0 ? 'series' : type as 'movie' | 'series'} />;
}
return (
@@ -634,7 +634,7 @@ const MetadataScreen: React.FC = () => {
watchProgressOpacity={animations.watchProgressOpacity}
watchProgressWidth={animations.watchProgressWidth}
watchProgress={watchProgressData.watchProgress}
- type={type as 'movie' | 'series'}
+ type={Object.keys(groupedEpisodes).length > 0 ? 'series' : type as 'movie' | 'series'}
getEpisodeDetails={watchProgressData.getEpisodeDetails}
handleShowStreams={handleShowStreams}
handleToggleLibrary={handleToggleLibrary}
@@ -655,11 +655,11 @@ const MetadataScreen: React.FC = () => {
0 ? 'series' : type as 'movie' | 'series'}
contentId={id}
loadingMetadata={false}
renderRatings={() => imdbId && shouldLoadSecondaryData ? (
-
+ 0 ? 'show' : 'movie'} />
) : null}
/>
@@ -681,7 +681,7 @@ const MetadataScreen: React.FC = () => {
)}
{/* Series/Movie Content with episode skeleton when loading */}
- {type === 'series' ? (
+ {Object.keys(groupedEpisodes).length > 0 ? (
{
// Skip processing if component is unmounting
if (!isMounted.current) return;
- const currentStreamsData = type === 'series' ? episodeStreams : groupedStreams;
+ const currentStreamsData = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
+ if (__DEV__) console.log('[StreamsScreen] streams state changed', { providerKeys: Object.keys(currentStreamsData || {}), type });
// Update available providers immediately when streams change
const providersWithStreams = Object.entries(currentStreamsData)
@@ -680,6 +681,7 @@ export const StreamsScreen = () => {
// Only update if we have new providers, don't remove existing ones during loading
setAvailableProviders(prevProviders => {
const newProviders = new Set([...prevProviders, ...providersWithStreamsSet]);
+ if (__DEV__) console.log('[StreamsScreen] availableProviders ->', Array.from(newProviders));
return newProviders;
});
}
@@ -701,6 +703,7 @@ export const StreamsScreen = () => {
changed = true;
}
});
+ if (changed && __DEV__) console.log('[StreamsScreen] loadingProviders ->', nextLoading);
return changed ? nextLoading : prevLoading;
});
@@ -717,7 +720,7 @@ export const StreamsScreen = () => {
}
// Check if provider exists in current streams data
- const currentStreamsData = type === 'series' ? episodeStreams : groupedStreams;
+ const currentStreamsData = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
const hasStreamsForProvider = currentStreamsData[selectedProvider] &&
currentStreamsData[selectedProvider].streams &&
currentStreamsData[selectedProvider].streams.length > 0;
@@ -733,14 +736,19 @@ export const StreamsScreen = () => {
// Update useEffect to check for sources
useEffect(() => {
const checkProviders = async () => {
+ if (__DEV__) console.log('[StreamsScreen] checkProviders() start', { id, type, episodeId, fromPlayer });
+ logger.log(`[StreamsScreen] checkProviders() start id=${id} type=${type} episodeId=${episodeId || 'none'} fromPlayer=${!!fromPlayer}`);
// Check for Stremio addons
const hasStremioProviders = await stremioService.hasStreamProviders();
+ if (__DEV__) console.log('[StreamsScreen] hasStremioProviders:', hasStremioProviders);
// Check for local scrapers (only if enabled in settings)
const hasLocalScrapers = settings.enableLocalScrapers && await localScraperService.hasScrapers();
+ if (__DEV__) console.log('[StreamsScreen] hasLocalScrapers:', hasLocalScrapers, 'enableLocalScrapers:', settings.enableLocalScrapers);
// We have providers if we have either Stremio addons OR enabled local scrapers
const hasProviders = hasStremioProviders || hasLocalScrapers;
+ logger.log(`[StreamsScreen] provider check: hasProviders=${hasProviders}`);
if (!isMounted.current) return;
@@ -748,22 +756,43 @@ export const StreamsScreen = () => {
setHasStremioStreamProviders(hasStremioProviders);
if (!hasProviders) {
+ logger.log('[StreamsScreen] No providers detected; scheduling no-sources UI');
const timer = setTimeout(() => {
if (isMounted.current) setShowNoSourcesError(true);
}, 500);
return () => clearTimeout(timer);
} else {
- if (type === 'series' && episodeId) {
+ if ((type === 'series' || type === 'other') && episodeId) {
logger.log(`🎬 Loading episode streams for: ${episodeId}`);
setLoadingProviders({
'stremio': true
});
setSelectedEpisode(episodeId);
setStreamsLoadStart(Date.now());
+ if (__DEV__) console.log('[StreamsScreen] calling loadEpisodeStreams', episodeId);
loadEpisodeStreams(episodeId);
} else if (type === 'movie') {
logger.log(`🎬 Loading movie streams for: ${id}`);
setStreamsLoadStart(Date.now());
+ if (__DEV__) console.log('[StreamsScreen] calling loadStreams (movie)', id);
+ loadStreams();
+ } else if ((type === 'series' || type === 'other') && !episodeId) {
+ // Series with no episodes (e.g., TV/live channels) – fetch streams directly
+ logger.log(`🎬 Loading series streams (no episodes) for: ${id}`);
+ setLoadingProviders({
+ 'stremio': true
+ });
+ setStreamsLoadStart(Date.now());
+ if (__DEV__) console.log('[StreamsScreen] calling loadStreams (series no episodeId)', id);
+ loadStreams();
+ } else if (type === 'tv') {
+ // TV/live content – fetch streams directly
+ logger.log(`📺 Loading TV streams for: ${id}`);
+ setLoadingProviders({
+ 'stremio': true
+ });
+ setStreamsLoadStart(Date.now());
+ if (__DEV__) console.log('[StreamsScreen] calling loadStreams (tv)', id);
loadStreams();
}
@@ -972,7 +1001,7 @@ export const StreamsScreen = () => {
await new Promise(resolve => setTimeout(resolve, 50));
// Prepare available streams for the change source feature
- const streamsToPass = type === 'series' ? episodeStreams : groupedStreams;
+ const streamsToPass = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
// Determine the stream name using the same logic as StreamCard
const streamName = stream.name || stream.title || 'Unnamed Stream';
@@ -1027,9 +1056,9 @@ export const StreamsScreen = () => {
navigation.navigate(playerRoute as any, {
uri: stream.url,
title: metadata?.name || '',
- episodeTitle: type === 'series' ? currentEpisode?.name : undefined,
- season: type === 'series' ? currentEpisode?.season_number : undefined,
- episode: type === 'series' ? currentEpisode?.episode_number : undefined,
+ episodeTitle: (type === 'series' || type === 'other') ? currentEpisode?.name : undefined,
+ season: (type === 'series' || type === 'other') ? currentEpisode?.season_number : undefined,
+ episode: (type === 'series' || type === 'other') ? currentEpisode?.episode_number : undefined,
quality: (stream.title?.match(/(\d+)p/) || [])[1] || undefined,
year: metadata?.year,
streamProvider: streamProvider,
@@ -1040,7 +1069,7 @@ export const StreamsScreen = () => {
forceVlc,
id,
type,
- episodeId: type === 'series' && selectedEpisode ? selectedEpisode : undefined,
+ episodeId: (type === 'series' || type === 'other') && selectedEpisode ? selectedEpisode : undefined,
imdbId: imdbId || undefined,
availableStreams: streamsToPass,
backdrop: bannerImage || undefined,
@@ -1221,8 +1250,8 @@ export const StreamsScreen = () => {
const success = await VideoPlayerService.playVideo(stream.url, {
useExternalPlayer: true,
title: metadata?.name || 'Video',
- episodeTitle: type === 'series' ? currentEpisode?.name : undefined,
- episodeNumber: type === 'series' && currentEpisode ? `S${currentEpisode.season_number}E${currentEpisode.episode_number}` : undefined,
+ episodeTitle: (type === 'series' || type === 'other') ? currentEpisode?.name : undefined,
+ episodeNumber: (type === 'series' || type === 'other') && currentEpisode ? `S${currentEpisode.season_number}E${currentEpisode.episode_number}` : undefined,
});
if (!success) {
@@ -1280,7 +1309,7 @@ export const StreamsScreen = () => {
!autoplayTriggered &&
isAutoplayWaiting
) {
- const streams = type === 'series' ? episodeStreams : groupedStreams;
+ const streams = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
if (Object.keys(streams).length > 0) {
const bestStream = getBestStream(streams);
@@ -1311,7 +1340,7 @@ export const StreamsScreen = () => {
const filterItems = useMemo(() => {
const installedAddons = stremioService.getInstalledAddons();
- const streams = type === 'series' ? episodeStreams : groupedStreams;
+ const streams = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
// Make sure we include all providers with streams, not just those in availableProviders
const allProviders = new Set([
@@ -1389,7 +1418,7 @@ export const StreamsScreen = () => {
}, [availableProviders, type, episodeStreams, groupedStreams, settings.streamDisplayMode]);
const sections = useMemo(() => {
- const streams = type === 'series' ? episodeStreams : groupedStreams;
+ const streams = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
const installedAddons = stremioService.getInstalledAddons();
// Filter streams by selected provider
@@ -1681,8 +1710,8 @@ export const StreamsScreen = () => {
});
}, [episodeImage, bannerImage, metadata]);
- const isLoading = type === 'series' ? loadingEpisodeStreams : loadingStreams;
- const streams = type === 'series' ? episodeStreams : groupedStreams;
+ const isLoading = (type === 'series' || (type === 'other' && selectedEpisode)) ? loadingEpisodeStreams : loadingStreams;
+ const streams = (type === 'series' || (type === 'other' && selectedEpisode)) ? episodeStreams : groupedStreams;
// Determine extended loading phases
const streamsEmpty = Object.keys(streams).length === 0;
@@ -1747,7 +1776,7 @@ export const StreamsScreen = () => {
>
- {type === 'series' ? 'Back to Episodes' : 'Back to Info'}
+ {(type === 'series' || (type === 'other' && selectedEpisode)) ? 'Back to Episodes' : 'Back to Info'}
@@ -1772,7 +1801,7 @@ export const StreamsScreen = () => {
)}
- {type === 'series' && (
+ {(type === 'series' || (type === 'other' && selectedEpisode)) && (
{
showAlert={(t, m) => openAlert(t, m)}
parentTitle={metadata?.name}
parentType={type as 'movie' | 'series'}
- parentSeason={type === 'series' ? currentEpisode?.season_number : undefined}
- parentEpisode={type === 'series' ? currentEpisode?.episode_number : undefined}
- parentEpisodeTitle={type === 'series' ? currentEpisode?.name : undefined}
+ parentSeason={(type === 'series' || type === 'other') ? currentEpisode?.season_number : undefined}
+ parentEpisode={(type === 'series' || type === 'other') ? currentEpisode?.episode_number : undefined}
+ parentEpisodeTitle={(type === 'series' || type === 'other') ? currentEpisode?.name : undefined}
parentPosterUrl={episodeImage || metadata?.poster || undefined}
providerName={streams && Object.keys(streams).find(pid => (streams as any)[pid]?.streams?.includes?.(item))}
/>
diff --git a/src/services/stremioService.ts b/src/services/stremioService.ts
index a3e727a5..5bd5326a 100644
--- a/src/services/stremioService.ts
+++ b/src/services/stremioService.ts
@@ -639,7 +639,8 @@ class StremioService {
// Special handling for Cinemeta
if (manifest.id === 'com.linvo.cinemeta') {
const baseUrl = 'https://v3-cinemeta.strem.io';
- let url = `${baseUrl}/catalog/${type}/${id}.json`;
+ const encodedId = encodeURIComponent(id);
+ let url = `${baseUrl}/catalog/${type}/${encodedId}.json`;
// Add paging
url += `?skip=${(page - 1) * this.DEFAULT_PAGE_SIZE}`;
@@ -670,9 +671,10 @@ class StremioService {
try {
const { baseUrl, queryParams } = this.getAddonBaseURL(manifest.url);
-
+
// Build the catalog URL
- let url = `${baseUrl}/catalog/${type}/${id}.json`;
+ const encodedId = encodeURIComponent(id);
+ let url = `${baseUrl}/catalog/${type}/${encodedId}.json`;
// Add paging
url += `?skip=${(page - 1) * this.DEFAULT_PAGE_SIZE}`;
@@ -710,12 +712,15 @@ class StremioService {
// If a preferred addon is specified, try it first
if (preferredAddonId) {
+ logger.log(`🔍 [getMetaDetails] Looking for preferred addon: ${preferredAddonId}`);
const preferredAddon = addons.find(addon => addon.id === preferredAddonId);
+ logger.log(`🔍 [getMetaDetails] Found preferred addon: ${preferredAddon ? preferredAddon.id : 'null'}`);
if (preferredAddon && preferredAddon.resources) {
// Build URL for metadata request
const { baseUrl, queryParams } = this.getAddonBaseURL(preferredAddon.url || '');
- const url = queryParams ? `${baseUrl}/meta/${type}/${id}.json?${queryParams}` : `${baseUrl}/meta/${type}/${id}.json`;
+ const encodedId = encodeURIComponent(id);
+ const url = queryParams ? `${baseUrl}/meta/${type}/${encodedId}.json?${queryParams}` : `${baseUrl}/meta/${type}/${encodedId}.json`;
// Check if addon supports meta resource for this type
let hasMetaSupport = false;
@@ -744,7 +749,8 @@ class StremioService {
if (hasMetaSupport) {
try {
-
+ logger.log(`🔗 [${preferredAddon.name}] Requesting metadata: ${url} (preferred, id=${id}, type=${type})`);
+
const response = await this.retryRequest(async () => {
return await axios.get(url, { timeout: 10000 });
});
@@ -767,7 +773,8 @@ class StremioService {
for (const baseUrl of cinemetaUrls) {
try {
- const url = `${baseUrl}/meta/${type}/${id}.json`;
+ const encodedId = encodeURIComponent(id);
+ const url = `${baseUrl}/meta/${type}/${encodedId}.json`;
const response = await this.retryRequest(async () => {
return await axios.get(url, { timeout: 10000 });
@@ -786,8 +793,9 @@ class StremioService {
for (const addon of addons) {
if (!addon.resources || addon.id === 'com.linvo.cinemeta' || addon.id === preferredAddonId) continue;
- // Check if addon supports meta resource for this type (handles both string and object formats)
+ // Check if addon supports meta resource for this type AND idPrefix (handles both string and object formats)
let hasMetaSupport = false;
+ let supportsIdPrefix = false;
for (const resource of addon.resources) {
// Check if the current element is a ResourceObject
@@ -797,6 +805,12 @@ class StremioService {
Array.isArray(typedResource.types) &&
typedResource.types.includes(type)) {
hasMetaSupport = true;
+ // Match idPrefixes if present; otherwise assume support
+ if (Array.isArray(typedResource.idPrefixes) && typedResource.idPrefixes.length > 0) {
+ supportsIdPrefix = typedResource.idPrefixes.some(p => id.startsWith(p));
+ } else {
+ supportsIdPrefix = true;
+ }
break;
}
}
@@ -804,18 +818,25 @@ class StremioService {
else if (typeof resource === 'string' && resource === 'meta' && addon.types) {
if (Array.isArray(addon.types) && addon.types.includes(type)) {
hasMetaSupport = true;
+ // For simple resources, check addon-level idPrefixes if present
+ if (addon.idPrefixes && Array.isArray(addon.idPrefixes) && addon.idPrefixes.length > 0) {
+ supportsIdPrefix = addon.idPrefixes.some(p => id.startsWith(p));
+ } else {
+ supportsIdPrefix = true;
+ }
break;
}
}
}
-
- if (!hasMetaSupport) continue;
+ // Require both meta support and idPrefix compatibility
+ if (!(hasMetaSupport && supportsIdPrefix)) continue;
try {
const { baseUrl, queryParams } = this.getAddonBaseURL(addon.url || '');
- const url = queryParams ? `${baseUrl}/meta/${type}/${id}.json?${queryParams}` : `${baseUrl}/meta/${type}/${id}.json`;
-
- logger.log(`🔗 [${addon.name}] Requesting metadata: ${url}`);
+ const encodedId = encodeURIComponent(id);
+ const url = queryParams ? `${baseUrl}/meta/${type}/${encodedId}.json?${queryParams}` : `${baseUrl}/meta/${type}/${encodedId}.json`;
+
+ logger.log(`🔗 [${addon.name}] Requesting metadata: ${url} (id=${id}, type=${type})`);
const response = await this.retryRequest(async () => {
return await axios.get(url, { timeout: 10000 });
});
@@ -978,19 +999,17 @@ class StremioService {
if (idType === 'imdb') {
const tmdbService = TMDBService.getInstance();
const tmdbIdNumber = await tmdbService.findTMDBIdByIMDB(baseId);
-
if (tmdbIdNumber) {
tmdbId = tmdbIdNumber.toString();
} else {
- return; // Skip local scrapers if we can't convert the ID
+ logger.log('🔧 [getStreams] Skipping local scrapers: could not convert IMDb to TMDB for', baseId);
}
- } else {
+ } else if (idType === 'kitsu') {
// For kitsu IDs, skip local scrapers as they don't support kitsu
logger.log('🔧 [getStreams] Skipping local scrapers for kitsu ID:', baseId);
- // Don't return here - continue to Stremio addon processing
}
} catch (error) {
- return; // Skip local scrapers if ID parsing fails
+ logger.warn('🔧 [getStreams] Skipping local scrapers due to ID parsing error:', error);
}
// Execute local scrapers asynchronously with TMDB ID (only for IMDb IDs)
@@ -1006,6 +1025,8 @@ class StremioService {
}
}
});
+ } else {
+ logger.log('🔧 [getStreams] Local scrapers not executed for this ID/type; continuing with Stremio addons');
}
}
}
@@ -1033,21 +1054,6 @@ class StremioService {
let hasStreamResource = false;
let supportsIdPrefix = false;
- // Extract ID prefix from the ID
- let idPrefix = id.split(':')[0];
-
- // For IMDb IDs (tt...), extract just the 'tt' prefix
- if (idPrefix.startsWith('tt')) {
- idPrefix = 'tt';
- }
- // For Kitsu IDs, keep the full prefix
- else if (idPrefix === 'kitsu') {
- idPrefix = 'kitsu';
- }
- // For other prefixes, keep as is
-
- logger.log(`🔍 [getStreams] Checking if addon supports ID prefix: ${idPrefix} (from ${id.split(':')[0]})`);
-
// Iterate through the resources array, checking each element
for (const resource of addon.resources) {
// Check if the current element is a ResourceObject
@@ -1058,10 +1064,10 @@ class StremioService {
typedResource.types.includes(type)) {
hasStreamResource = true;
- // Check if this addon supports the ID prefix
- if (Array.isArray(typedResource.idPrefixes)) {
- supportsIdPrefix = typedResource.idPrefixes.includes(idPrefix);
- logger.log(`🔍 [getStreams] Addon ${addon.id} supports prefixes: ${typedResource.idPrefixes.join(', ')}`);
+ // Check if this addon supports the ID prefix (generic: any prefix that matches start of id)
+ if (Array.isArray(typedResource.idPrefixes) && typedResource.idPrefixes.length > 0) {
+ supportsIdPrefix = typedResource.idPrefixes.some(p => id.startsWith(p));
+ logger.log(`🔍 [getStreams] Addon ${addon.id} supports prefixes: ${typedResource.idPrefixes.join(', ')} → matches=${supportsIdPrefix}`);
} else {
// If no idPrefixes specified, assume it supports all prefixes
supportsIdPrefix = true;
@@ -1074,10 +1080,10 @@ class StremioService {
else if (typeof resource === 'string' && resource === 'stream' && addon.types) {
if (Array.isArray(addon.types) && addon.types.includes(type)) {
hasStreamResource = true;
- // For simple string resources, check addon-level idPrefixes
- if (addon.idPrefixes && Array.isArray(addon.idPrefixes)) {
- supportsIdPrefix = addon.idPrefixes.includes(idPrefix);
- logger.log(`🔍 [getStreams] Addon ${addon.id} supports prefixes: ${addon.idPrefixes.join(', ')}`);
+ // For simple string resources, check addon-level idPrefixes (generic)
+ if (addon.idPrefixes && Array.isArray(addon.idPrefixes) && addon.idPrefixes.length > 0) {
+ supportsIdPrefix = addon.idPrefixes.some(p => id.startsWith(p));
+ logger.log(`🔍 [getStreams] Addon ${addon.id} supports prefixes: ${addon.idPrefixes.join(', ')} → matches=${supportsIdPrefix}`);
} else {
// If no idPrefixes specified, assume it supports all prefixes
supportsIdPrefix = true;
@@ -1093,9 +1099,9 @@ class StremioService {
if (!hasStreamResource) {
logger.log(`❌ [getStreams] Addon ${addon.id} does not support streaming ${type}`);
} else if (!supportsIdPrefix) {
- logger.log(`❌ [getStreams] Addon ${addon.id} supports ${type} but not ID prefix ${idPrefix}`);
+ logger.log(`❌ [getStreams] Addon ${addon.id} supports ${type} but its idPrefixes did not match id=${id}`);
} else {
- logger.log(`✅ [getStreams] Addon ${addon.id} supports streaming ${type} with prefix ${idPrefix}`);
+ logger.log(`✅ [getStreams] Addon ${addon.id} supports streaming ${type} for id=${id}`);
}
return canHandleRequest;
@@ -1122,7 +1128,8 @@ class StremioService {
}
const { baseUrl, queryParams } = this.getAddonBaseURL(addon.url);
- const url = queryParams ? `${baseUrl}/stream/${type}/${id}.json?${queryParams}` : `${baseUrl}/stream/${type}/${id}.json`;
+ const encodedId = encodeURIComponent(id);
+ const url = queryParams ? `${baseUrl}/stream/${type}/${encodedId}.json?${queryParams}` : `${baseUrl}/stream/${type}/${encodedId}.json`;
logger.log(`🔗 [getStreams] Requesting streams from ${addon.name} (${addon.id}): ${url}`);
@@ -1165,7 +1172,8 @@ class StremioService {
}
const { baseUrl, queryParams } = this.getAddonBaseURL(addon.url);
- const streamPath = `/stream/${type}/${id}.json`;
+ const encodedId = encodeURIComponent(id);
+ const streamPath = `/stream/${type}/${encodedId}.json`;
const url = queryParams ? `${baseUrl}${streamPath}?${queryParams}` : `${baseUrl}${streamPath}`;
logger.log(`Fetching streams from URL: ${url}`);
@@ -1363,10 +1371,11 @@ class StremioService {
const { baseUrl } = this.getAddonBaseURL(addon.url || '');
let url = '';
if (type === 'series' && videoId) {
- const episodeInfo = videoId.replace('series:', '');
+ const episodeInfo = encodeURIComponent(videoId.replace('series:', ''));
url = `${baseUrl}/subtitles/series/${episodeInfo}.json`;
} else {
- url = `${baseUrl}/subtitles/${type}/${id}.json`;
+ const encodedId = encodeURIComponent(id);
+ url = `${baseUrl}/subtitles/${type}/${encodedId}.json`;
}
logger.log(`Fetching subtitles from ${addon.name}: ${url}`);
const response = await this.retryRequest(async () => axios.get(url, { timeout: 10000 }));