NuvioStreaming/ScraperforTesting/uhdmovies.js
2025-07-21 00:42:13 +05:30

1234 lines
No EOL
47 KiB
JavaScript

const axios = require('axios');
const cheerio = require('cheerio');
const { URLSearchParams, URL } = require('url');
const FormData = require('form-data');
const { CookieJar } = require('tough-cookie');
const fs = require('fs').promises;
const path = require('path');
const RedisCache = require('../utils/redisCache');
// Dynamic import for axios-cookiejar-support
let axiosCookieJarSupport = null;
const getAxiosCookieJarSupport = async () => {
if (!axiosCookieJarSupport) {
axiosCookieJarSupport = await import('axios-cookiejar-support');
}
return axiosCookieJarSupport;
};
// --- Domain Fetching ---
let uhdMoviesDomain = 'https://uhdmovies.email'; // Fallback domain
let domainCacheTimestamp = 0;
const DOMAIN_CACHE_TTL = 4 * 60 * 60 * 1000; // 4 hours
async function getUHDMoviesDomain() {
const now = Date.now();
if (now - domainCacheTimestamp < DOMAIN_CACHE_TTL) {
return uhdMoviesDomain;
}
try {
console.log('[UHDMovies] Fetching latest domain...');
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;
console.log(`[UHDMovies] Updated domain to: ${uhdMoviesDomain}`);
} else {
console.warn('[UHDMovies] Domain JSON fetched, but "UHDMovies" key was not found. Using fallback.');
}
} catch (error) {
console.error(`[UHDMovies] Failed to fetch latest domain, using fallback. Error: ${error.message}`);
}
return uhdMoviesDomain;
}
// Constants
const TMDB_API_KEY_UHDMOVIES = "439c478a771f35c05022f9feabcca01c"; // Public TMDB API key
// --- Caching Configuration ---
const CACHE_ENABLED = process.env.DISABLE_CACHE !== 'true'; // Set to true to disable caching for this provider
console.log(`[UHDMovies] Internal cache is ${CACHE_ENABLED ? 'enabled' : 'disabled'}.`);
const CACHE_DIR = process.env.VERCEL ? path.join('/tmp', '.uhd_cache') : path.join(__dirname, '.cache', 'uhdmovies'); // Cache directory inside providers/uhdmovies
// Initialize Redis cache
const redisCache = new RedisCache('UHDMovies');
// --- Caching Helper Functions ---
const ensureCacheDir = async () => {
if (!CACHE_ENABLED) return;
try {
await fs.mkdir(CACHE_DIR, { recursive: true });
} catch (error) {
if (error.code !== 'EEXIST') {
console.error(`[UHDMovies Cache] Error creating cache directory: ${error.message}`);
}
}
};
const getFromCache = async (key) => {
if (!CACHE_ENABLED) return null;
// Try Redis cache first, then fallback to file system
const cachedData = await redisCache.getFromCache(key, '', CACHE_DIR);
if (cachedData) {
return cachedData.data || cachedData; // Support both new format (data field) and legacy format
}
return null;
};
const saveToCache = async (key, data) => {
if (!CACHE_ENABLED) return;
const cacheData = {
data: data
};
// Save to both Redis and file system
await redisCache.saveToCache(key, cacheData, '', CACHE_DIR);
};
// Initialize cache directory on startup
ensureCacheDir();
// Configure axios with headers to mimic a browser
const axiosInstance = axios.create({
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',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Cache-Control': 'max-age=0'
}
});
// Simple In-Memory Cache
const uhdMoviesCache = {
search: {},
movie: {},
show: {}
};
// Function to search for movies
async function searchMovies(query) {
try {
const baseUrl = await getUHDMoviesDomain();
console.log(`[UHDMovies] Searching for: ${query}`);
const searchUrl = `${baseUrl}/search/${encodeURIComponent(query)}`;
const response = await axiosInstance.get(searchUrl);
const $ = cheerio.load(response.data);
const searchResults = [];
// New logic for grid-based search results
$('article.gridlove-post').each((index, element) => {
const linkElement = $(element).find('a[href*="/download-"]');
if (linkElement.length > 0) {
const link = linkElement.first().attr('href');
// Prefer the 'title' attribute, fallback to h1 text
const title = linkElement.first().attr('title') || $(element).find('h1.sanket').text().trim();
if (link && title && !searchResults.some(item => item.link === link)) {
searchResults.push({
title,
link: link.startsWith('http') ? link : `${baseUrl}${link}`
});
}
}
});
// Fallback for original list-based search if new logic fails
if (searchResults.length === 0) {
console.log('[UHDMovies] Grid search logic found no results, trying original list-based logic...');
$('a[href*="/download-"]').each((index, element) => {
const link = $(element).attr('href');
// Avoid duplicates by checking if link already exists in results
if (link && !searchResults.some(item => item.link === link)) {
const title = $(element).text().trim();
if (title) {
searchResults.push({
title,
link: link.startsWith('http') ? link : `${baseUrl}${link}`
});
}
}
});
}
console.log(`[UHDMovies] Found ${searchResults.length} results`);
return searchResults;
} catch (error) {
console.error(`[UHDMovies] Error searching movies: ${error.message}`);
return [];
}
}
// Function to extract clean quality information from verbose text
function extractCleanQuality(fullQualityText) {
if (!fullQualityText || fullQualityText === 'Unknown Quality') {
return 'Unknown Quality';
}
const cleanedFullQualityText = fullQualityText.replace(/(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g, '').trim();
const text = cleanedFullQualityText.toLowerCase();
let quality = [];
// Extract resolution
if (text.includes('2160p') || text.includes('4k')) {
quality.push('4K');
} else if (text.includes('1080p')) {
quality.push('1080p');
} else if (text.includes('720p')) {
quality.push('720p');
} else if (text.includes('480p')) {
quality.push('480p');
}
// Extract special features
if (text.includes('hdr')) {
quality.push('HDR');
}
if (text.includes('dolby vision') || text.includes('dovi') || /\bdv\b/.test(text)) {
quality.push('DV');
}
if (text.includes('imax')) {
quality.push('IMAX');
}
if (text.includes('bluray') || text.includes('blu-ray')) {
quality.push('BluRay');
}
// If we found any quality indicators, join them
if (quality.length > 0) {
return quality.join(' | ');
}
// Fallback: try to extract a shorter version of the original text
// Look for patterns like "Movie Name (Year) Resolution ..."
const patterns = [
/(\d{3,4}p.*?(?:x264|x265|hevc).*?)[\[\(]/i,
/(\d{3,4}p.*?)[\[\(]/i,
/((?:720p|1080p|2160p|4k).*?)$/i
];
for (const pattern of patterns) {
const match = cleanedFullQualityText.match(pattern);
if (match && match[1].trim().length < 100) {
return match[1].trim().replace(/x265/ig, 'HEVC');
}
}
// Final fallback: truncate if too long
if (cleanedFullQualityText.length > 80) {
return cleanedFullQualityText.substring(0, 77).replace(/x265/ig, 'HEVC') + '...';
}
return cleanedFullQualityText.replace(/x265/ig, 'HEVC');
}
// Function to extract download links for TV shows from a page
async function extractTvShowDownloadLinks(showPageUrl, season, episode) {
try {
console.log(`[UHDMovies] Extracting TV show links from: ${showPageUrl} for S${season}E${episode}`);
const response = await axiosInstance.get(showPageUrl);
const $ = cheerio.load(response.data);
const showTitle = $('h1').first().text().trim();
const downloadLinks = [];
// --- NEW LOGIC TO SCOPE SEARCH TO THE CORRECT SEASON ---
let inTargetSeason = false;
let qualityText = '';
$('.entry-content').find('*').each((index, element) => {
const $el = $(element);
const text = $el.text().trim();
const seasonMatch = text.match(/^SEASON\s+(\d+)/i);
// Check if we are entering a new season block
if (seasonMatch) {
const currentSeasonNum = parseInt(seasonMatch[1], 10);
if (currentSeasonNum == season) {
inTargetSeason = true;
console.log(`[UHDMovies] Entering Season ${season} block.`);
} else if (inTargetSeason) {
// We've hit the next season, so we stop.
console.log(`[UHDMovies] Exiting Season ${season} block, now in Season ${currentSeasonNum}.`);
inTargetSeason = false;
return false; // Exit .each() loop
}
}
if (inTargetSeason) {
// This element is within the correct season's block.
// Is this a quality header? (e.g., a <pre> or a <p> with <strong>)
// It often contains resolution, release group, etc.
const isQualityHeader = $el.is('pre, p:has(strong), p:has(b), h3, h4');
if (isQualityHeader) {
const headerText = $el.text().trim();
// Filter out irrelevant headers. We can be more aggressive here.
if (headerText.length > 5 && !/plot|download|screenshot|trailer|join|powered by|season/i.test(headerText) && !($el.find('a').length > 0)) {
qualityText = headerText; // Store the most recent quality header
}
}
// Is this a paragraph with episode links?
if ($el.is('p') && $el.find('a[href*="tech.unblockedgames.world"], a[href*="tech.examzculture.in"]').length > 0) {
const linksParagraph = $el;
const episodeRegex = new RegExp(`^Episode\\s+0*${episode}(?!\\d)`, 'i');
const targetEpisodeLink = linksParagraph.find('a').filter((i, el) => {
return episodeRegex.test($(el).text().trim());
}).first();
if (targetEpisodeLink.length > 0) {
const link = targetEpisodeLink.attr('href');
if (link && !downloadLinks.some(item => item.link === link)) {
const sizeMatch = qualityText.match(/\[\s*([0-9.,]+\s*[KMGT]B)/i);
const size = sizeMatch ? sizeMatch[1] : 'Unknown';
const cleanQuality = extractCleanQuality(qualityText);
const rawQuality = qualityText.replace(/(\r\n|\n|\r)/gm, " ").replace(/\s+/g, ' ').trim();
console.log(`[UHDMovies] Found match: Quality='${qualityText}', Link='${link}'`);
downloadLinks.push({ quality: cleanQuality, size: size, link: link, rawQuality: rawQuality });
}
}
}
}
});
if (downloadLinks.length === 0) {
console.log('[UHDMovies] Main extraction logic failed. Trying fallback method without season scoping.');
$('.entry-content').find('a[href*="tech.unblockedgames.world"], a[href*="tech.examzculture.in"]').each((i, el) => {
const linkElement = $(el);
const episodeRegex = new RegExp(`^Episode\\s+0*${episode}(?!\\d)`, 'i');
if (episodeRegex.test(linkElement.text().trim())) {
const link = linkElement.attr('href');
if (link && !downloadLinks.some(item => item.link === link)) {
let qualityText = 'Unknown Quality';
const parentP = linkElement.closest('p, div');
const prevElement = parentP.prev();
if (prevElement.length > 0) {
const prevText = prevElement.text().trim();
if (prevText && prevText.length > 5 && !prevText.toLowerCase().includes('download')) {
qualityText = prevText;
}
}
const sizeMatch = qualityText.match(/\[([0-9.,]+[KMGT]B[^\]]*)\]/i);
const size = sizeMatch ? sizeMatch[1] : 'Unknown';
const cleanQuality = extractCleanQuality(qualityText);
const rawQuality = qualityText.replace(/(\r\n|\n|\r)/gm, " ").replace(/\s+/g, ' ').trim();
console.log(`[UHDMovies] Found match via fallback: Quality='${qualityText}', Link='${link}'`);
downloadLinks.push({ quality: cleanQuality, size: size, link: link, rawQuality: rawQuality });
}
}
});
}
if (downloadLinks.length > 0) {
console.log(`[UHDMovies] Found ${downloadLinks.length} links for S${season}E${episode}.`);
} else {
console.log(`[UHDMovies] Could not find links for S${season}E${episode}. It's possible the logic needs adjustment or the links aren't on the page.`);
}
return { title: showTitle, links: downloadLinks };
} catch (error) {
console.error(`[UHDMovies] Error extracting TV show download links: ${error.message}`);
return { title: 'Unknown', links: [] };
}
}
// Function to extract download links from a movie page
async function extractDownloadLinks(moviePageUrl, targetYear = null) {
try {
console.log(`[UHDMovies] Extracting links from: ${moviePageUrl}`);
const response = await axiosInstance.get(moviePageUrl);
const $ = cheerio.load(response.data);
const movieTitle = $('h1').first().text().trim();
const downloadLinks = [];
// Find all download links (the new SID links) and their associated quality information
$('a[href*="tech.unblockedgames.world"], a[href*="tech.examzculture.in"]').each((index, element) => {
const link = $(element).attr('href');
if (link && !downloadLinks.some(item => item.link === link)) {
let quality = 'Unknown Quality';
let size = 'Unknown';
// Method 1: Look for quality in the closest preceding paragraph or heading
const prevElement = $(element).closest('p').prev();
if (prevElement.length > 0) {
const prevText = prevElement.text().trim();
if (prevText && prevText.length > 20 && !prevText.includes('Download')) {
quality = prevText;
}
}
// Method 2: Look for quality in parent's siblings
if (quality === 'Unknown Quality') {
const parentSiblings = $(element).parent().prevAll().first().text().trim();
if (parentSiblings && parentSiblings.length > 20) {
quality = parentSiblings;
}
}
// Method 3: Look for bold/strong text above the link
if (quality === 'Unknown Quality') {
const strongText = $(element).closest('p').prevAll().find('strong, b').last().text().trim();
if (strongText && strongText.length > 20) {
quality = strongText;
}
}
// Method 4: Look for the entire paragraph containing quality info
if (quality === 'Unknown Quality') {
let currentElement = $(element).parent();
for (let i = 0; i < 5; i++) {
currentElement = currentElement.prev();
if (currentElement.length === 0) break;
const text = currentElement.text().trim();
if (text && text.length > 30 &&
(text.includes('1080p') || text.includes('720p') || text.includes('2160p') ||
text.includes('4K') || text.includes('HEVC') || text.includes('x264') || text.includes('x265'))) {
quality = text;
break;
}
}
}
// Year-based filtering for collections
if (targetYear && quality !== 'Unknown Quality') {
// Check for years in quality text
const yearMatches = quality.match(/\((\d{4})\)/g);
let hasMatchingYear = false;
if (yearMatches && yearMatches.length > 0) {
for (const yearMatch of yearMatches) {
const year = parseInt(yearMatch.replace(/[()]/g, ''));
if (year === targetYear) {
hasMatchingYear = true;
break;
}
}
if (!hasMatchingYear) {
console.log(`[UHDMovies] Skipping link due to year mismatch. Target: ${targetYear}, Found: ${yearMatches.join(', ')} in "${quality}"`);
return; // Skip this link
}
} else {
// If no year in quality text, check filename and other indicators
const linkText = $(element).text().trim();
const parentText = $(element).parent().text().trim();
const combinedText = `${quality} ${linkText} ${parentText}`;
// Look for years in combined text
const allYearMatches = combinedText.match(/\((\d{4})\)/g) || combinedText.match(/(\d{4})/g);
if (allYearMatches) {
let foundTargetYear = false;
for (const yearMatch of allYearMatches) {
const year = parseInt(yearMatch.replace(/[()]/g, ''));
if (year >= 1900 && year <= 2030) { // Valid movie year range
if (year === targetYear) {
foundTargetYear = true;
break;
}
}
}
if (!foundTargetYear && allYearMatches.length > 0) {
console.log(`[UHDMovies] Skipping link due to no matching year found. Target: ${targetYear}, Found years: ${allYearMatches.join(', ')} in combined text`);
return; // Skip this link
}
}
// Additional check: if quality contains movie names that don't match target year
const lowerQuality = quality.toLowerCase();
if (targetYear === 2015) {
if (lowerQuality.includes('wasp') || lowerQuality.includes('quantumania')) {
console.log(`[UHDMovies] Skipping link for 2015 target as it contains 'wasp' or 'quantumania': "${quality}"`);
return; // Skip this link
}
}
}
}
// Extract size from quality text if present
const sizeMatch = quality.match(/\[([0-9.,]+\s*[KMGT]B[^\]]*)\]/);
if (sizeMatch) {
size = sizeMatch[1];
}
// Clean up the quality information
const cleanQuality = extractCleanQuality(quality);
downloadLinks.push({
quality: cleanQuality,
size: size,
link: link,
rawQuality: quality.replace(/(\r\n|\n|\r)/gm, " ").replace(/\s+/g, ' ').trim()
});
}
});
return {
title: movieTitle,
links: downloadLinks
};
} catch (error) {
console.error(`[UHDMovies] Error extracting download links: ${error.message}`);
return { title: 'Unknown', links: [] };
}
}
function extractCodecs(rawQuality) {
const codecs = [];
const text = rawQuality.toLowerCase();
if (text.includes('hevc') || text.includes('x265')) {
codecs.push('H.265');
} else if (text.includes('x264')) {
codecs.push('H.264');
}
if (text.includes('10bit') || text.includes('10-bit')) {
codecs.push('10-bit');
}
if (text.includes('atmos')) {
codecs.push('Atmos');
} else if (text.includes('dts-hd')) {
codecs.push('DTS-HD');
} else if (text.includes('dts')) {
codecs.push('DTS');
} else if (text.includes('ddp5.1') || text.includes('dd+ 5.1') || text.includes('eac3')) {
codecs.push('EAC3');
} else if (text.includes('ac3')) {
codecs.push('AC3');
}
if (text.includes('dovi') || text.includes('dolby vision') || /\bdv\b/.test(text)) {
codecs.push('DV');
} else if (text.includes('hdr')) {
codecs.push('HDR');
}
return codecs;
}
// Function to try Instant Download method
async function tryInstantDownload($) {
const instantDownloadLink = $('a:contains("Instant Download")').attr('href');
if (!instantDownloadLink) {
return null;
}
console.log('[UHDMovies] Found "Instant Download" link, attempting to extract final URL...');
try {
const urlParams = new URLSearchParams(new URL(instantDownloadLink).search);
const keys = urlParams.get('url');
if (keys) {
const apiUrl = `${new URL(instantDownloadLink).origin}/api`;
const formData = new FormData();
formData.append('keys', keys);
const apiResponse = await axiosInstance.post(apiUrl, formData, {
headers: {
...formData.getHeaders(),
'x-token': new URL(instantDownloadLink).hostname
}
});
if (apiResponse.data && apiResponse.data.url) {
let finalUrl = apiResponse.data.url;
// Fix spaces in workers.dev URLs by encoding them properly
if (finalUrl.includes('workers.dev')) {
const urlParts = finalUrl.split('/');
const filename = urlParts[urlParts.length - 1];
const encodedFilename = filename.replace(/ /g, '%20');
urlParts[urlParts.length - 1] = encodedFilename;
finalUrl = urlParts.join('/');
}
console.log('[UHDMovies] Extracted final link from API:', finalUrl);
return finalUrl;
}
}
console.log('[UHDMovies] Could not find a valid final download link from Instant Download.');
return null;
} catch (error) {
console.log(`[UHDMovies] Error processing "Instant Download": ${error.message}`);
return null;
}
}
// Function to try Resume Cloud method
async function tryResumeCloud($) {
// Look for both "Resume Cloud" and "Cloud Resume Download" buttons
const resumeCloudButton = $('a:contains("Resume Cloud"), a:contains("Cloud Resume Download")');
if (resumeCloudButton.length === 0) {
return null;
}
const resumeLink = resumeCloudButton.attr('href');
if (!resumeLink) {
return null;
}
// Check if it's already a direct download link (workers.dev)
if (resumeLink.includes('workers.dev') || resumeLink.startsWith('http')) {
let directLink = resumeLink;
// Fix spaces in workers.dev URLs by encoding them properly
if (directLink.includes('workers.dev')) {
const urlParts = directLink.split('/');
const filename = urlParts[urlParts.length - 1];
const encodedFilename = filename.replace(/ /g, '%20');
urlParts[urlParts.length - 1] = encodedFilename;
directLink = urlParts.join('/');
}
console.log(`[UHDMovies] Found direct "Cloud Resume Download" link: ${directLink}`);
return directLink;
}
// Otherwise, follow the link to get the final download
try {
const resumeUrl = new URL(resumeLink, 'https://driveleech.net').href;
console.log(`[UHDMovies] Found 'Resume Cloud' page link. Following to: ${resumeUrl}`);
// "Click" the link by making another request
const finalPageResponse = await axiosInstance.get(resumeUrl, { maxRedirects: 10 });
const $$ = cheerio.load(finalPageResponse.data);
// Look for direct download links
let finalDownloadLink = $$('a.btn-success[href*="workers.dev"], a[href*="driveleech.net/d/"]').attr('href');
if (finalDownloadLink) {
// Fix spaces in workers.dev URLs by encoding them properly
if (finalDownloadLink.includes('workers.dev')) {
// Split the URL at the last slash to separate the base URL from the filename
const urlParts = finalDownloadLink.split('/');
const filename = urlParts[urlParts.length - 1];
// Encode spaces in the filename part only
const encodedFilename = filename.replace(/ /g, '%20');
urlParts[urlParts.length - 1] = encodedFilename;
finalDownloadLink = urlParts.join('/');
}
console.log(`[UHDMovies] Extracted final Resume Cloud link: ${finalDownloadLink}`);
return finalDownloadLink;
} else {
console.log('[UHDMovies] Could not find the final download link on the "Resume Cloud" page.');
return null;
}
} catch (error) {
console.log(`[UHDMovies] Error processing "Resume Cloud": ${error.message}`);
return null;
}
}
// Environment variable to control URL validation
const URL_VALIDATION_ENABLED = process.env.DISABLE_URL_VALIDATION !== 'true';
console.log(`[UHDMovies] URL validation is ${URL_VALIDATION_ENABLED ? 'enabled' : 'disabled'}.`);
// Validate if a video URL is working (not 404 or broken)
async function validateVideoUrl(url, timeout = 10000) {
// Skip validation if disabled via environment variable
if (!URL_VALIDATION_ENABLED) {
console.log(`[UHDMovies] URL validation disabled, skipping validation for: ${url.substring(0, 100)}...`);
return true;
}
try {
console.log(`[UHDMovies] Validating URL: ${url.substring(0, 100)}...`);
const response = await axiosInstance.head(url, {
timeout,
headers: {
'Range': 'bytes=0-1' // Just request first byte to test
}
});
// Check if status is OK (200-299) or partial content (206)
if (response.status >= 200 && response.status < 400) {
console.log(`[UHDMovies] ✓ URL validation successful (${response.status})`);
return true;
} else {
console.log(`[UHDMovies] ✗ URL validation failed with status: ${response.status}`);
return false;
}
} catch (error) {
console.log(`[UHDMovies] ✗ URL validation failed: ${error.message}`);
return false;
}
}
// Function to follow redirect links and get the final download URL with size info
async function getFinalLink(redirectUrl) {
try {
console.log(`[UHDMovies] Following redirect: ${redirectUrl}`);
// Request the driveleech page
let response = await axiosInstance.get(redirectUrl, { maxRedirects: 10 });
let $ = cheerio.load(response.data);
// --- Check for JavaScript redirect ---
const scriptContent = $('script').html();
const redirectMatch = scriptContent && scriptContent.match(/window\.location\.replace\("([^"]+)"\)/);
if (redirectMatch && redirectMatch[1]) {
const newPath = redirectMatch[1];
const newUrl = new URL(newPath, 'https://driveleech.net/').href;
console.log(`[UHDMovies] Found JavaScript redirect. Following to: ${newUrl}`);
response = await axiosInstance.get(newUrl, { maxRedirects: 10 });
$ = cheerio.load(response.data);
}
// Extract size and filename information from the page
let sizeInfo = 'Unknown';
let fileName = null;
const sizeElement = $('li.list-group-item:contains("Size :")').text();
if (sizeElement) {
const sizeMatch = sizeElement.match(/Size\s*:\s*([0-9.,]+\s*[KMGT]B)/i);
if (sizeMatch) sizeInfo = sizeMatch[1];
}
const nameElement = $('li.list-group-item:contains("Name :")').text();
if (nameElement) {
fileName = nameElement.replace('Name :', '').trim();
}
// Try each download method in order until we find a working one
const downloadMethods = [
{ name: 'Resume Cloud', func: tryResumeCloud },
{ name: 'Instant Download', func: tryInstantDownload }
];
for (const method of downloadMethods) {
try {
console.log(`[UHDMovies] Trying ${method.name}...`);
const finalUrl = await method.func($);
if (finalUrl) {
// Validate the URL before using it
const isValid = await validateVideoUrl(finalUrl);
if (isValid) {
console.log(`[UHDMovies] ✓ Successfully resolved using ${method.name}`);
return { url: finalUrl, size: sizeInfo, fileName: fileName };
} else {
console.log(`[UHDMovies] ✗ ${method.name} returned invalid/broken URL, trying next method...`);
}
} else {
console.log(`[UHDMovies] ✗ ${method.name} failed to resolve URL, trying next method...`);
}
} catch (error) {
console.log(`[UHDMovies] ✗ ${method.name} threw error: ${error.message}, trying next method...`);
}
}
console.log('[UHDMovies] ✗ All download methods failed.');
return null;
} catch (error) {
console.error(`[UHDMovies] Error in getFinalLink: ${error.message}`);
return null;
}
}
// Compare media to find matching result
function compareMedia(mediaInfo, searchResult) {
const normalizeString = (str) => String(str || '').toLowerCase().replace(/[^a-zA-Z0-9]/g, '');
const titleWithAnd = mediaInfo.title.replace(/\s*&\s*/g, ' and ');
const normalizedMediaTitle = normalizeString(titleWithAnd);
const normalizedResultTitle = normalizeString(searchResult.title);
console.log(`[UHDMovies] Comparing: "${mediaInfo.title}" (${mediaInfo.year}) vs "${searchResult.title}"`);
console.log(`[UHDMovies] Normalized: "${normalizedMediaTitle}" vs "${normalizedResultTitle}"`);
// Check if titles match or result title contains media title
let titleMatches = normalizedResultTitle.includes(normalizedMediaTitle);
// If direct match fails, try checking for franchise/collection matches
if (!titleMatches) {
const mainTitle = normalizedMediaTitle.split('and')[0];
const isCollection = normalizedResultTitle.includes('duology') ||
normalizedResultTitle.includes('trilogy') ||
normalizedResultTitle.includes('quadrilogy') ||
normalizedResultTitle.includes('collection') ||
normalizedResultTitle.includes('saga');
if (isCollection && normalizedResultTitle.includes(mainTitle)) {
console.log(`[UHDMovies] Found collection match: "${mainTitle}" in collection "${searchResult.title}"`);
titleMatches = true;
}
}
if (!titleMatches) {
console.log(`[UHDMovies] Title mismatch: "${normalizedResultTitle}" does not contain "${normalizedMediaTitle}"`);
return false;
}
// NEW: Negative keyword check for spinoffs
const negativeKeywords = ['challenge', 'conversation', 'story', 'in conversation'];
const originalTitleLower = mediaInfo.title.toLowerCase();
for (const keyword of negativeKeywords) {
if (normalizedResultTitle.includes(keyword.replace(/\s/g, '')) && !originalTitleLower.includes(keyword)) {
console.log(`[UHDMovies] Rejecting spinoff due to keyword: "${keyword}"`);
return false; // It's a spinoff, reject it.
}
}
// Check year if both are available
if (mediaInfo.year && searchResult.title) {
const yearRegex = /\b(19[89]\d|20\d{2})\b/g; // Look for years 1980-2099
const yearMatchesInResult = searchResult.title.match(yearRegex);
const yearRangeMatch = searchResult.title.match(/\((\d{4})\s*-\s*(\d{4})\)/);
let hasMatchingYear = false;
if (yearMatchesInResult) {
console.log(`[UHDMovies] Found years in result: ${yearMatchesInResult.join(', ')}`);
if (yearMatchesInResult.some(yearStr => Math.abs(parseInt(yearStr) - mediaInfo.year) <= 1)) {
hasMatchingYear = true;
}
}
if (!hasMatchingYear && yearRangeMatch) {
console.log(`[UHDMovies] Found year range in result: ${yearRangeMatch[0]}`);
const startYear = parseInt(yearRangeMatch[1]);
const endYear = parseInt(yearRangeMatch[2]);
if (mediaInfo.year >= startYear - 1 && mediaInfo.year <= endYear + 1) {
hasMatchingYear = true;
}
}
// If there are any years found in the title, one of them MUST match.
if ((yearMatchesInResult || yearRangeMatch) && !hasMatchingYear) {
console.log(`[UHDMovies] Year mismatch. Target: ${mediaInfo.year}, but no matching year found in result.`);
return false;
}
}
console.log(`[UHDMovies] Match successful!`);
return true;
}
// Function to score search results based on quality keywords
function scoreResult(title) {
let score = 0;
const lowerTitle = title.toLowerCase();
if (lowerTitle.includes('remux')) score += 10;
if (lowerTitle.includes('bluray') || lowerTitle.includes('blu-ray')) score += 8;
if (lowerTitle.includes('imax')) score += 6;
if (lowerTitle.includes('4k') || lowerTitle.includes('2160p')) score += 5;
if (lowerTitle.includes('dovi') || lowerTitle.includes('dolby vision') || /\\bdv\\b/.test(lowerTitle)) score += 4;
if (lowerTitle.includes('hdr')) score += 3;
if (lowerTitle.includes('1080p')) score += 2;
if (lowerTitle.includes('hevc') || lowerTitle.includes('x265')) score += 1;
return score;
}
// Function to parse size string into MB
function parseSize(sizeString) {
if (!sizeString || typeof sizeString !== 'string') {
return 0;
}
const upperCaseSizeString = sizeString.toUpperCase();
// Regex to find a number (integer or float) followed by GB, MB, or KB
const match = upperCaseSizeString.match(/([0-9.,]+)\s*(GB|MB|KB)/);
if (!match) {
return 0;
}
const sizeValue = parseFloat(match[1].replace(/,/g, ''));
if (isNaN(sizeValue)) {
return 0;
}
const unit = match[2];
if (unit === 'GB') {
return sizeValue * 1024;
} else if (unit === 'MB') {
return sizeValue;
} else if (unit === 'KB') {
return sizeValue / 1024;
}
return 0;
}
// New function to resolve the tech.unblockedgames.world links
async function resolveSidToDriveleech(sidUrl) {
console.log(`[UHDMovies] Resolving SID link: ${sidUrl}`);
const { origin } = new URL(sidUrl);
const jar = new CookieJar();
// Get the wrapper function from dynamic import
const { wrapper } = await getAxiosCookieJarSupport();
const session = wrapper(axios.create({
jar,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'en-US,en;q=0.5',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1'
}
}));
try {
// Step 0: Get the _wp_http value
console.log(" [SID] Step 0: Fetching initial page...");
const responseStep0 = await session.get(sidUrl);
let $ = cheerio.load(responseStep0.data);
const initialForm = $('#landing');
const wp_http_step1 = initialForm.find('input[name="_wp_http"]').val();
const action_url_step1 = initialForm.attr('action');
if (!wp_http_step1 || !action_url_step1) {
console.error(" [SID] Error: Could not find _wp_http in initial form.");
return null;
}
// Step 1: POST to the first form's action URL
console.log(" [SID] Step 1: Submitting initial form...");
const step1Data = new URLSearchParams({ '_wp_http': wp_http_step1 });
const responseStep1 = await session.post(action_url_step1, step1Data, {
headers: { 'Referer': sidUrl, 'Content-Type': 'application/x-www-form-urlencoded' }
});
// Step 2: Parse verification page for second form
console.log(" [SID] Step 2: Parsing verification page...");
$ = cheerio.load(responseStep1.data);
const verificationForm = $('#landing');
const action_url_step2 = verificationForm.attr('action');
const wp_http2 = verificationForm.find('input[name="_wp_http2"]').val();
const token = verificationForm.find('input[name="token"]').val();
if (!action_url_step2) {
console.error(" [SID] Error: Could not find verification form.");
return null;
}
// Step 3: POST to the verification URL
console.log(" [SID] Step 3: Submitting verification...");
const step2Data = new URLSearchParams({ '_wp_http2': wp_http2, 'token': token });
const responseStep2 = await session.post(action_url_step2, step2Data, {
headers: { 'Referer': responseStep1.request.res.responseUrl, 'Content-Type': 'application/x-www-form-urlencoded' }
});
// Step 4: Find dynamic cookie and link from JavaScript
console.log(" [SID] Step 4: Parsing final page for JS data...");
let finalLinkPath = null;
let cookieName = null;
let cookieValue = null;
const scriptContent = responseStep2.data;
const cookieMatch = scriptContent.match(/s_343\('([^']+)',\s*'([^']+)'/);
const linkMatch = scriptContent.match(/c\.setAttribute\("href",\s*"([^"]+)"\)/);
if (cookieMatch) {
cookieName = cookieMatch[1].trim();
cookieValue = cookieMatch[2].trim();
}
if (linkMatch) {
finalLinkPath = linkMatch[1].trim();
}
if (!finalLinkPath || !cookieName || !cookieValue) {
console.error(" [SID] Error: Could not extract dynamic cookie/link from JS.");
return null;
}
const finalUrl = new URL(finalLinkPath, origin).href;
console.log(` [SID] Dynamic link found: ${finalUrl}`);
console.log(` [SID] Dynamic cookie found: ${cookieName}`);
// Step 5: Set cookie and make final request
console.log(" [SID] Step 5: Setting cookie and making final request...");
await jar.setCookie(`${cookieName}=${cookieValue}`, origin);
const finalResponse = await session.get(finalUrl, {
headers: { 'Referer': responseStep2.request.res.responseUrl }
});
// Step 6: Extract driveleech URL from meta refresh tag
$ = cheerio.load(finalResponse.data);
const metaRefresh = $('meta[http-equiv="refresh"]');
if (metaRefresh.length > 0) {
const content = metaRefresh.attr('content');
const urlMatch = content.match(/url=(.*)/i);
if (urlMatch && urlMatch[1]) {
const driveleechUrl = urlMatch[1].replace(/"/g, "").replace(/'/g, "");
console.log(` [SID] SUCCESS! Resolved Driveleech URL: ${driveleechUrl}`);
return driveleechUrl;
}
}
console.error(" [SID] Error: Could not find meta refresh tag with Driveleech URL.");
return null;
} catch (error) {
console.error(` [SID] Error during SID resolution: ${error.message}`);
if (error.response) {
console.error(` [SID] Status: ${error.response.status}`);
}
return null;
}
}
// Main function to get streams for TMDB content
async function getUHDMoviesStreams(tmdbId, mediaType = 'movie', season = null, episode = null) {
console.log(`[UHDMovies] Attempting to fetch streams for TMDB ID: ${tmdbId}, Type: ${mediaType}${mediaType === 'tv' ? `, S:${season}E:${episode}` : ''}`);
const cacheKey = `uhd_final_v12_${tmdbId}_${mediaType}${season ? `_s${season}e${episode}` : ''}`;
try {
// 1. Check cache first
let cachedLinks = await getFromCache(cacheKey);
if (cachedLinks && cachedLinks.length > 0) {
console.log(`[UHDMovies] Cache HIT for ${cacheKey}. Using ${cachedLinks.length} cached Driveleech links.`);
} else {
if (cachedLinks && cachedLinks.length === 0) {
console.log(`[UHDMovies] Cache contains empty data for ${cacheKey}. Refetching from source.`);
} else {
console.log(`[UHDMovies] Cache MISS for ${cacheKey}. Fetching from source.`);
}
console.log(`[UHDMovies] Cache MISS for ${cacheKey}. Fetching from source.`);
// 2. If cache miss, get TMDB info to perform search
const tmdbUrl = `https://api.themoviedb.org/3/${mediaType === 'tv' ? 'tv' : 'movie'}/${tmdbId}?api_key=${TMDB_API_KEY_UHDMOVIES}`;
const tmdbResponse = await axios.get(tmdbUrl);
const tmdbData = tmdbResponse.data;
const mediaInfo = {
title: mediaType === 'tv' ? tmdbData.name : tmdbData.title,
year: parseInt(((mediaType === 'tv' ? tmdbData.first_air_date : tmdbData.release_date) || '').split('-')[0], 10)
};
if (!mediaInfo.title) throw new Error('Could not extract title from TMDB response.');
console.log(`[UHDMovies] TMDB Info: "${mediaInfo.title}" (${mediaInfo.year || 'N/A'})`);
// 3. Search for the media on UHDMovies
let searchTitle = mediaInfo.title.replace(/:/g, '').replace(/\s*&\s*/g, ' and ');
let searchResults = await searchMovies(searchTitle);
// If no results or only wrong year results, try fallback search with just main title
if (searchResults.length === 0 || !searchResults.some(result => compareMedia(mediaInfo, result))) {
console.log(`[UHDMovies] Primary search failed or no matches. Trying fallback search...`);
// Extract main title (remove subtitles after colon, "and the", etc.)
let fallbackTitle = mediaInfo.title.split(':')[0].trim();
if (fallbackTitle.includes('and the')) {
fallbackTitle = fallbackTitle.split('and the')[0].trim();
}
if (fallbackTitle !== searchTitle) {
console.log(`[UHDMovies] Fallback search with: "${fallbackTitle}"`);
const fallbackResults = await searchMovies(fallbackTitle);
if (fallbackResults.length > 0) {
searchResults = fallbackResults;
}
}
}
if (searchResults.length === 0) {
console.log(`[UHDMovies] No search results found for "${mediaInfo.title}".`);
// Don't cache empty results to allow retrying later
return [];
}
// 4. Find the best matching result
const matchingResults = searchResults.filter(result => compareMedia(mediaInfo, result));
if (matchingResults.length === 0) {
console.log(`[UHDMovies] No matching content found for "${mediaInfo.title}" (${mediaInfo.year}).`);
// Don't cache empty results to allow retrying later
return [];
}
let matchingResult;
if (matchingResults.length === 1) {
matchingResult = matchingResults[0];
} else {
console.log(`[UHDMovies] Found ${matchingResults.length} matching results. Scoring to find the best...`);
const scoredResults = matchingResults.map(result => {
const score = scoreResult(result.title);
console.log(` - Score ${score}: ${result.title}`);
return { ...result, score };
}).sort((a, b) => b.score - a.score);
matchingResult = scoredResults[0];
console.log(`[UHDMovies] Best match selected with score ${matchingResult.score}: "${matchingResult.title}"`);
}
console.log(`[UHDMovies] Found matching content: "${matchingResult.title}"`);
// 5. Extract SID links from the movie/show page
const downloadInfo = await (mediaType === 'tv' ? extractTvShowDownloadLinks(matchingResult.link, season, episode) : extractDownloadLinks(matchingResult.link, mediaInfo.year));
if (downloadInfo.links.length === 0) {
console.log('[UHDMovies] No download links found on page.');
// Don't cache empty results to allow retrying later
return [];
}
// 6. Resolve all SID links to driveleech redirect URLs (intermediate step)
console.log(`[UHDMovies] Resolving ${downloadInfo.links.length} SID link(s) to driveleech redirect URLs...`);
const resolutionPromises = downloadInfo.links.map(async (linkInfo) => {
try {
let driveleechUrl = null;
if (linkInfo.link && (linkInfo.link.includes('tech.unblockedgames.world') || linkInfo.link.includes('tech.creativeexpressionsblog.com') || linkInfo.link.includes('tech.examzculture.in'))) {
driveleechUrl = await resolveSidToDriveleech(linkInfo.link);
} else if (linkInfo.link && (linkInfo.link.includes('driveseed.org') || linkInfo.link.includes('driveleech.net'))) {
// If it's already a direct driveseed/driveleech link, use it
driveleechUrl = linkInfo.link;
}
if (!driveleechUrl) return null;
console.log(`[UHDMovies] Caching driveleech redirect URL for ${linkInfo.quality}: ${driveleechUrl}`);
return { ...linkInfo, driveleechRedirectUrl: driveleechUrl };
} catch (error) {
console.error(`[UHDMovies] Error resolving ${linkInfo.quality}: ${error.message}`);
return null;
}
});
cachedLinks = (await Promise.all(resolutionPromises)).filter(Boolean);
// 7. Save the successfully resolved driveleech redirect URLs to the cache
if (cachedLinks.length > 0) {
console.log(`[UHDMovies] Caching ${cachedLinks.length} resolved driveleech redirect URLs for key: ${cacheKey}`);
await saveToCache(cacheKey, cachedLinks);
} else {
console.log(`[UHDMovies] No driveleech redirect URLs could be resolved. Not caching to allow retrying later.`);
return [];
}
}
if (!cachedLinks || cachedLinks.length === 0) {
console.log('[UHDMovies] No final file page URLs found after scraping/cache check.');
return [];
}
// 8. Process all cached driveleech redirect URLs to get streaming links
console.log(`[UHDMovies] Processing ${cachedLinks.length} cached driveleech redirect URL(s) to get streaming links.`);
const streamPromises = cachedLinks.map(async (linkInfo) => {
try {
// First, resolve the driveleech redirect URL to get the final file page URL
const response = await axiosInstance.get(linkInfo.driveleechRedirectUrl, { maxRedirects: 10 });
let $ = cheerio.load(response.data);
// Check for JavaScript redirect (window.location.replace)
const scriptContent = $('script').html();
const redirectMatch = scriptContent && scriptContent.match(/window\.location\.replace\("([^"]+)"\)/);
let finalFilePageUrl = linkInfo.driveleechRedirectUrl;
if (redirectMatch && redirectMatch[1]) {
finalFilePageUrl = new URL(redirectMatch[1], 'https://driveleech.net/').href;
console.log(`[UHDMovies] Resolved redirect to final file page: ${finalFilePageUrl}`);
// Load the final file page
const finalResponse = await axiosInstance.get(finalFilePageUrl, { maxRedirects: 10 });
$ = cheerio.load(finalResponse.data);
}
// Extract file size and name information
let sizeInfo = 'Unknown';
let fileName = null;
const sizeElement = $('li.list-group-item:contains("Size :")').text();
if (sizeElement) {
const sizeMatch = sizeElement.match(/Size\s*:\s*([0-9.,]+\s*[KMGT]B)/);
if (sizeMatch) {
sizeInfo = sizeMatch[1];
}
}
const nameElement = $('li.list-group-item:contains("Name :")');
if (nameElement.length > 0) {
fileName = nameElement.text().replace('Name :', '').trim();
} else {
const h5Title = $('div.card-header h5').clone().children().remove().end().text().trim();
if (h5Title) {
fileName = h5Title.replace(/\[.*\]/, '').trim();
}
}
// Try download methods to get final streaming URL
const downloadMethods = [
{ name: 'Resume Cloud', func: tryResumeCloud },
{ name: 'Instant Download', func: tryInstantDownload }
];
for (const method of downloadMethods) {
try {
const finalUrl = await method.func($);
if (finalUrl) {
const isValid = await validateVideoUrl(finalUrl);
if (isValid) {
const rawQuality = linkInfo.rawQuality || '';
const codecs = extractCodecs(rawQuality);
const cleanFileName = fileName ? fileName.replace(/\.[^/.]+$/, "").replace(/[._]/g, ' ') : (linkInfo.quality || 'Unknown');
return {
name: `UHDMovies`,
title: `${cleanFileName}\n${sizeInfo}`,
url: finalUrl,
quality: linkInfo.quality,
size: sizeInfo,
fileName: fileName,
fullTitle: rawQuality,
codecs: codecs,
behaviorHints: { bingeGroup: `uhdmovies-${linkInfo.quality}` }
};
}
}
} catch (error) {
console.log(`[UHDMovies] ${method.name} failed: ${error.message}`);
}
}
return null;
} catch (error) {
console.error(`[UHDMovies] Error processing cached driveleech redirect ${linkInfo.driveleechRedirectUrl}: ${error.message}`);
return null;
}
});
const streams = (await Promise.all(streamPromises)).filter(Boolean);
console.log(`[UHDMovies] Successfully processed ${streams.length} final stream links.`);
// Sort final streams by size
streams.sort((a, b) => {
const sizeA = parseSize(a.size);
const sizeB = parseSize(b.size);
return sizeB - sizeA;
});
return streams;
} catch (error) {
console.error(`[UHDMovies] A critical error occurred in getUHDMoviesStreams for ${tmdbId}: ${error.message}`);
if (error.stack) console.error(error.stack);
return [];
}
}
module.exports = { getUHDMoviesStreams };