diff --git a/ScraperforTesting/uhdmovies.js b/ScraperforTesting/uhdmovies.js index 92db95f5..aa325b46 100644 --- a/ScraperforTesting/uhdmovies.js +++ b/ScraperforTesting/uhdmovies.js @@ -29,7 +29,7 @@ async function getUHDMoviesDomain() { try { console.log('[UHDMovies] Fetching latest domain...'); - const response = await axios.get('https://raw.githubusercontent.com/phisher98/TVVVV/refs/heads/main/domains.json', { timeout: 10000 }); + const response = await axios.get('https://raw.githubusercontent.com/phisher98/TVVVV/refs/heads/main/domains.json'); if (response.data && response.data.UHDMovies) { uhdMoviesDomain = response.data.UHDMovies; domainCacheTimestamp = now; @@ -101,8 +101,7 @@ const axiosInstance = axios.create({ 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', 'Cache-Control': 'max-age=0' - }, - timeout: 30000 + } }); // Simple In-Memory Cache diff --git a/local-scrapers-repo b/local-scrapers-repo index fa92f8cf..6be646d6 160000 --- a/local-scrapers-repo +++ b/local-scrapers-repo @@ -1 +1 @@ -Subproject commit fa92f8cf27fc238b48697263aa48d8a2eb5a68ef +Subproject commit 6be646d6702a809b28eac63b7d04a453f756c402 diff --git a/src/services/localScraperService.ts b/src/services/localScraperService.ts index 645311ca..fbe7d729 100644 --- a/src/services/localScraperService.ts +++ b/src/services/localScraperService.ts @@ -294,6 +294,9 @@ class LocalScraperService { // This is a simplified sandbox - in production, you'd want more security try { // Create a limited global context + const moduleExports = {}; + const moduleObj = { exports: moduleExports }; + const sandbox = { console: { log: (...args: any[]) => logger.log('[Scraper]', ...args), @@ -310,13 +313,41 @@ class LocalScraperService { parseFloat, encodeURIComponent, decodeURIComponent, + // Add fetch for HTTP requests (using axios as polyfill) + fetch: async (url: string, options: any = {}) => { + const axiosConfig = { + url, + method: options.method || 'GET', + headers: options.headers || {}, + data: options.body, + timeout: 30000 + }; + + try { + const response = await axios(axiosConfig); + return { + ok: response.status >= 200 && response.status < 300, + status: response.status, + statusText: response.statusText, + headers: response.headers, + json: async () => response.data, + text: async () => typeof response.data === 'string' ? response.data : JSON.stringify(response.data) + }; + } catch (error: any) { + throw new Error(`Fetch failed: ${error.message}`); + } + }, // Add axios for HTTP requests axios: axios.create({ timeout: 30000, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' } - }) + }), + // Node.js compatibility + module: moduleObj, + exports: moduleExports, + global: {} // Empty global object }; // Execute the scraper code with timeout @@ -328,7 +359,7 @@ class LocalScraperService { try { // Create function from code const func = new Function('sandbox', 'params', ` - const { console, setTimeout, clearTimeout, Promise, JSON, Date, Math, parseInt, parseFloat, encodeURIComponent, decodeURIComponent, axios } = sandbox; + const { console, setTimeout, clearTimeout, Promise, JSON, Date, Math, parseInt, parseFloat, encodeURIComponent, decodeURIComponent, axios, fetch, module, exports, global } = sandbox; ${code} // Call the main function (assuming it's exported) @@ -336,6 +367,8 @@ class LocalScraperService { return getStreams(params.tmdbId, params.mediaType, params.season, params.episode); } else if (typeof module !== 'undefined' && module.exports && typeof module.exports.getStreams === 'function') { return module.exports.getStreams(params.tmdbId, params.mediaType, params.season, params.episode); + } else if (typeof global !== 'undefined' && global.getStreams && typeof global.getStreams === 'function') { + return global.getStreams(params.tmdbId, params.mediaType, params.season, params.episode); } else { throw new Error('No getStreams function found in scraper'); } diff --git a/src/services/stremioService.ts b/src/services/stremioService.ts index cc3b271f..be5286ff 100644 --- a/src/services/stremioService.ts +++ b/src/services/stremioService.ts @@ -4,6 +4,7 @@ import { logger } from '../utils/logger'; import EventEmitter from 'eventemitter3'; import { localScraperService } from './localScraperService'; import { DEFAULT_SETTINGS, AppSettings } from '../hooks/useSettings'; +import { TMDBService } from './tmdbService'; // Create an event emitter for addon changes export const addonEmitter = new EventEmitter(); @@ -631,8 +632,60 @@ class StremioService { if (hasScrapers) { logger.log('🔧 [getStreams] Executing local scrapers for', type, id); - // Execute local scrapers asynchronously - localScraperService.getStreams(type, id, undefined, undefined, (streams, scraperId, scraperName, error) => { + // Map Stremio types to local scraper types + const scraperType = type === 'series' ? 'tv' : type; + + // Parse the Stremio ID to extract IMDb ID and season/episode info + let tmdbId: string | null = null; + let season: number | undefined = undefined; + let episode: number | undefined = undefined; + + try { + const idParts = id.split(':'); + let baseImdbId: string; + + // Handle different episode ID formats + if (idParts[0] === 'series') { + // Format: series:imdbId:season:episode + baseImdbId = idParts[1]; + if (scraperType === 'tv' && idParts.length >= 4) { + season = parseInt(idParts[2], 10); + episode = parseInt(idParts[3], 10); + } + } else if (idParts[0].startsWith('tt')) { + // Format: imdbId:season:episode (direct IMDb ID) + baseImdbId = idParts[0]; + if (scraperType === 'tv' && idParts.length >= 3) { + season = parseInt(idParts[1], 10); + episode = parseInt(idParts[2], 10); + } + } else { + // Fallback: assume first part is the ID + baseImdbId = idParts[0]; + if (scraperType === 'tv' && idParts.length >= 3) { + season = parseInt(idParts[1], 10); + episode = parseInt(idParts[2], 10); + } + } + + // Convert IMDb ID to TMDB ID using TMDBService + const tmdbService = TMDBService.getInstance(); + const tmdbIdNumber = await tmdbService.findTMDBIdByIMDB(baseImdbId); + + if (tmdbIdNumber) { + tmdbId = tmdbIdNumber.toString(); + logger.log(`🔄 [getStreams] Converted IMDb ID ${baseImdbId} to TMDB ID ${tmdbId}${scraperType === 'tv' ? ` (S${season}E${episode})` : ''}`); + } else { + logger.warn(`⚠️ [getStreams] Could not convert IMDb ID ${baseImdbId} to TMDB ID`); + return; // Skip local scrapers if we can't convert the ID + } + } catch (error) { + logger.error(`❌ [getStreams] Failed to parse Stremio ID or convert to TMDB ID:`, error); + return; // Skip local scrapers if ID parsing fails + } + + // Execute local scrapers asynchronously with TMDB ID + localScraperService.getStreams(scraperType, tmdbId, season, episode, (streams, scraperId, scraperName, error) => { if (error) { logger.error(`❌ [getStreams] Local scraper ${scraperName} failed:`, error); if (callback) {