diff --git a/package.json b/package.json index 10f13a5..8dc5117 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,8 @@ "main": "index.js", "scripts": { "test": "node test-providers.js", + "test:single": "node test-providers.js", "test:provider": "node test-provider.js", - "test:provider:build": "npm run build && node test-provider.js", - "test:interactive": "npm run build && node interactive-test.js", - "test:quick": "npm run build && node quick-test.js", "test:ui": "npm run test:interactive", "build": "node build-simple.js", "build:dev": "cross-env SKIP_MINIFY=true node build-simple.js", diff --git a/test-config.js b/test-config.js deleted file mode 100644 index 786669f..0000000 --- a/test-config.js +++ /dev/null @@ -1,111 +0,0 @@ -// Common test configurations -// You can customize these and run them quickly - -const testConfigs = { - // UHD Tests - uhdMeta: { - provider: "uhd", - module: "meta", - function: "getMeta", - params: { - link: "https://uhdmovies.email/download-squid-game-season-2-hindi-1080p-2160p-4k/", - }, - }, - - // LuxMovies Tests - luxPosts: { - provider: "luxMovies", - module: "posts", - function: "getPosts", - params: { - url: "https://rogmovies.sbs/page/1/?s=pan", - }, - }, - - // PrimeMirror Tests - primeMirrorEpisodes: { - provider: "primeMirror", - module: "episodes", - function: "getEpisodes", - params: { - url: "0KMA7H0RHEPJA51SUBXKN9V6VA", - }, - }, - - primeMirrorMeta: { - provider: "primeMirror", - module: "meta", - function: "getMeta", - params: { - link: "https://www.netflixmirror.com/title/82020512", - }, - }, - - primeMirrorSearch: { - provider: "primeMirror", - module: "posts", - function: "getSearchPosts", - params: { - searchQuery: "breaking", - page: 1, - providerValue: "primeMirror", - }, - }, - - // CinemaLuxe Tests - cinemaLuxeEpisodes: { - provider: "cinemaLuxe", - module: "episodes", - function: "getEpisodes", - params: { - url: "https://cinemalux.net/?88fdac61e5=cVQxdnNXeGRIRXlZTEQ0bTZSZlFsT09qclNlQzExOUNwVk5JZ05JK1ZjbzVxSWt1SHZSZjdZUm5vVnZEOEd1QXlrdXhPdnNETHRHTnpPUUNFN3k3VVdpY0J0OW5rem10c1ZlZ2xRcjI2YjFWRm9Uc3FEeEd0aWZlNFBpOHJ6bms=", - }, - }, - - // Add more test configurations here as needed - // Template: - // yourTestName: { - // provider: 'providerName', - // module: 'moduleName', - // function: 'functionName', - // params: { - // // your parameters here - // } - // } -}; - -// Predefined test batches -const testBatches = { - // Test all meta functions - allMeta: [ - testConfigs.uhdMeta, - testConfigs.primeMirrorMeta, - // Add more meta tests - ], - - // Test all posts/search functions - allPosts: [ - testConfigs.luxPosts, - testConfigs.primeMirrorSearch, - // Add more posts tests - ], - - // Test all episode functions - allEpisodes: [ - testConfigs.primeMirrorEpisodes, - testConfigs.cinemaLuxeEpisodes, - // Add more episode tests - ], - - // Quick smoke test - test one function from each major provider - smokeTest: [ - testConfigs.uhdMeta, - testConfigs.luxPosts, - testConfigs.primeMirrorSearch, - ], -}; - -module.exports = { - testConfigs, - testBatches, -}; diff --git a/test-providers.js b/test-providers.js index e3c3299..0cba5ff 100644 --- a/test-providers.js +++ b/test-providers.js @@ -1,188 +1,590 @@ const axios = require("axios"); +const cheerio = require("cheerio"); const fs = require("fs"); const path = require("path"); +// Load extractors and utilities +let providerContext; +try { + const { getBaseUrl } = require("./dist/getBaseUrl.js"); + const { hubcloudExtracter } = require("./dist/hubcloudExtractor.js"); + const { gofileExtracter } = require("./dist/gofileExtracter.js"); + const { superVideoExtractor } = require("./dist/superVideoExtractor.js"); + const { gdFlixExtracter } = require("./dist/gdFlixExtractor.js"); + + providerContext = { + axios, + cheerio, + getBaseUrl, + commonHeaders: { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + }, + extractors: { + hubcloudExtracter, + gofileExtracter, + superVideoExtractor, + gdFlixExtracter, + }, + Crypto: {}, + }; +} catch (error) { + console.log( + "⚠️ Could not load provider context. Run 'npm run build' first." + ); + providerContext = null; +} + /** - * Provider testing utility + * Helper to pick random items from array + */ +function pickRandom(arr, count = 1) { + const shuffled = [...arr].sort(() => Math.random() - 0.5); + return count === 1 ? shuffled[0] : shuffled.slice(0, count); +} + +/** + * Sleep helper + */ +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** + * Provider testing utility - Full integration test */ class ProviderTester { - constructor(serverUrl = "http://localhost:3001") { - this.serverUrl = serverUrl; - this.axios = axios.create({ - baseURL: serverUrl, - timeout: 10000, - }); + constructor(options = {}) { + this.timeout = options.timeout || 30000; + this.postsToTest = options.postsToTest || 2; + this.linksToTest = options.linksToTest || 2; + this.signal = new AbortController().signal; + this.results = {}; } /** - * Test server connectivity + * Load provider module */ - async testConnection() { + loadModule(providerName, moduleName) { try { - const response = await this.axios.get("/health"); - console.log("✅ Server connection OK"); - return true; + const modulePath = `./dist/${providerName}/${moduleName}.js`; + // Clear cache to get fresh module + delete require.cache[require.resolve(modulePath)]; + return require(modulePath); } catch (error) { - console.error("❌ Server connection failed:", error.message); - return false; - } - } - - /** - * Test manifest endpoint - */ - async testManifest() { - try { - const response = await this.axios.get("/manifest.json"); - const providers = response.data; - - console.log(`✅ Manifest OK - Found ${providers.length} providers:`); - providers.forEach((p) => { - console.log(` 📦 ${p.display_name} (${p.value}) v${p.version}`); - }); - - return providers; - } catch (error) { - console.error("❌ Manifest test failed:", error.message); return null; } } /** - * Test individual provider modules + * Load manifest to get enabled providers + */ + loadManifest() { + try { + const manifestPath = path.join(__dirname, "manifest.json"); + if (!fs.existsSync(manifestPath)) { + console.log("⚠️ manifest.json not found"); + return []; + } + const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8")); + return manifest; + } catch (error) { + console.log("⚠️ Failed to load manifest:", error.message); + return []; + } + } + + /** + * Get available providers from dist folder (excluding disabled ones) + */ + getAvailableProviders() { + const distPath = path.join(__dirname, "dist"); + if (!fs.existsSync(distPath)) { + console.log("❌ dist folder not found. Run 'npm run build' first."); + return []; + } + + // Load manifest to check for disabled providers + const manifest = this.loadManifest(); + const disabledProviders = manifest + .filter((p) => p.disabled === true) + .map((p) => p.value); + + if (disabledProviders.length > 0) { + console.log( + `\n⏭️ Skipping disabled providers: ${disabledProviders.join(", ")}` + ); + } + + const providers = fs + .readdirSync(distPath, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name) + .filter((name) => { + // Skip disabled providers + if (disabledProviders.includes(name)) { + return false; + } + // Check if it has required modules + const hasRequired = [ + "catalog.js", + "posts.js", + "meta.js", + "stream.js", + ].every((file) => fs.existsSync(path.join(distPath, name, file))); + return hasRequired; + }); + + return providers; + } + + /** + * Test a single provider with full flow */ async testProvider(providerName) { - console.log(`\n🧪 Testing provider: ${providerName}`); + console.log(`\n${"=".repeat(60)}`); + console.log(`🧪 Testing Provider: ${providerName}`); + console.log("=".repeat(60)); - const modules = ["catalog", "posts", "meta", "stream", "episodes"]; - const results = {}; + const result = { + provider: providerName, + catalog: { success: false, data: null, error: null }, + posts: { success: false, data: null, error: null }, + meta: { success: false, data: null, error: null }, + episodes: { success: false, data: null, error: null, skipped: false }, + stream: { success: false, data: null, error: null, skipped: false }, + summary: { passed: 0, failed: 0, skipped: 0 }, + }; - for (const module of modules) { - try { - const response = await this.axios.get( - `/dist/${providerName}/${module}.js` + try { + // Step 1: Load and test catalog + console.log("\n📂 Step 1: Loading Catalog..."); + const catalogModule = this.loadModule(providerName, "catalog"); + if (!catalogModule) { + throw new Error("Catalog module not found"); + } + + const catalog = catalogModule.catalog || []; + // const genres = catalogModule.genres || []; + const allFilters = [...catalog]; + + if (allFilters.length === 0) { + throw new Error("No filters found in catalog"); + } + + result.catalog.success = true; + result.catalog.data = { + catalogCount: catalog.length, + }; + console.log(` ✅ Found ${catalog.length} catalog items`); + + // Pick a random filter + const randomFilter = pickRandom(allFilters); + console.log( + ` 🎲 Selected random filter: "${randomFilter.title}" (${randomFilter.filter})` + ); + + // Step 2: Test getPosts with random filter + console.log("\n📝 Step 2: Testing getPosts..."); + const postsModule = this.loadModule(providerName, "posts"); + if (!postsModule || !postsModule.getPosts) { + throw new Error("getPosts function not found"); + } + + const posts = await postsModule.getPosts({ + filter: randomFilter.filter, + page: 1, + providerValue: providerName, + signal: this.signal, + providerContext, + }); + + if (!Array.isArray(posts) || posts.length === 0) { + throw new Error("getPosts returned empty or invalid result"); + } + + result.posts.success = true; + result.posts.data = { count: posts.length }; + console.log(` ✅ Got ${posts.length} posts`); + + // Pick random posts to test + const postsToTest = pickRandom( + posts, + Math.min(this.postsToTest, posts.length) + ); + console.log( + ` 🎲 Selected ${postsToTest.length} random posts for meta testing` + ); + + // Step 3: Test getMeta with random posts + console.log("\n📋 Step 3: Testing getMeta..."); + const metaModule = this.loadModule(providerName, "meta"); + if (!metaModule || !metaModule.getMeta) { + throw new Error("getMeta function not found"); + } + + const metaResults = []; + for (const post of postsToTest) { + console.log(`\n 📌 Testing: "${post.title.substring(0, 50)}..."`); + console.log(` Link: ${post.link}`); + + try { + await sleep(500); // Small delay between requests + const meta = await metaModule.getMeta({ + link: post.link, + providerContext, + }); + + if (!meta || !meta.linkList) { + console.log( + ` ⚠️ Meta returned but linkList is empty/missing` + ); + continue; + } + + metaResults.push({ post, meta }); + console.log( + ` ✅ Got meta: type=${meta.type}, links=${meta.linkList.length}` + ); + + // Show link structure + meta.linkList.forEach((link, i) => { + const hasEpisodes = !!link.episodesLink; + const hasDirectLinks = + link.directLinks && link.directLinks.length > 0; + console.log( + ` [${i + 1}] ${link.title.substring(0, 30)} - ${ + hasEpisodes ? "📺 Episodes" : "" + }${hasDirectLinks ? "🎬 Direct" : ""}` + ); + }); + } catch (err) { + console.log(` ❌ Error: ${err.message}`); + } + } + + if (metaResults.length === 0) { + throw new Error("No valid meta data retrieved"); + } + + result.meta.success = true; + result.meta.data = { testedCount: metaResults.length }; + + // Step 4: Test episodes OR stream based on meta content + console.log("\n🔗 Step 4: Testing Episodes/Stream..."); + + // Find links with episodes + const episodeLinks = []; + const directLinks = []; + + for (const { meta } of metaResults) { + for (const link of meta.linkList) { + if (link.episodesLink) { + episodeLinks.push({ meta, link }); + } + if (link.directLinks && link.directLinks.length > 0) { + directLinks.push({ meta, link }); + } + } + } + + // Test episodes if available + if (episodeLinks.length > 0) { + console.log(`\n 📺 Found ${episodeLinks.length} episode links`); + const episodesModule = this.loadModule(providerName, "episodes"); + + if (episodesModule && episodesModule.getEpisodes) { + const testEpisodeLink = pickRandom(episodeLinks); + console.log( + ` 🎲 Testing episodes from: ${testEpisodeLink.link.title}` + ); + console.log(` URL: ${testEpisodeLink.link.episodesLink}`); + + try { + const episodes = await episodesModule.getEpisodes({ + url: testEpisodeLink.link.episodesLink, + providerContext, + }); + + if (Array.isArray(episodes) && episodes.length > 0) { + result.episodes.success = true; + result.episodes.data = { count: episodes.length }; + console.log(` ✅ Got ${episodes.length} episodes`); + + // Show first few episodes + episodes.slice(0, 3).forEach((ep, i) => { + console.log(` [${i + 1}] ${ep.title}`); + }); + + // Test stream with random episode + console.log(`\n 🎬 Testing stream with random episode...`); + const randomEpisode = pickRandom(episodes); + console.log(` Episode: ${randomEpisode.title}`); + + try { + const streamModule = this.loadModule(providerName, "stream"); + if (streamModule && streamModule.getStream) { + const streams = await streamModule.getStream({ + link: randomEpisode.link, + type: "series", + signal: this.signal, + providerContext, + }); + + if (Array.isArray(streams) && streams.length > 0) { + result.stream.success = true; + result.stream.data = { + count: streams.length, + type: "series", + }; + console.log(` ✅ Got ${streams.length} stream(s)`); + streams.forEach((s, i) => { + console.log( + ` [${i + 1}] ${s.server} - ${ + s.quality || "unknown" + } quality` + ); + }); + } else { + console.log(` ⚠️ No streams returned`); + result.stream.error = "No streams returned"; + } + } + } catch (err) { + console.log(` ❌ Stream error: ${err.message}`); + result.stream.error = err.message; + } + } else { + console.log(` ⚠️ No episodes returned`); + result.episodes.error = "No episodes returned"; + } + } catch (err) { + console.log(` ❌ Episodes error: ${err.message}`); + result.episodes.error = err.message; + } + } else { + console.log(` ⚠️ getEpisodes function not found`); + result.episodes.skipped = true; + result.episodes.error = "Function not available"; + } + } else { + result.episodes.skipped = true; + console.log(` ℹ️ No episode links found, skipping episodes test`); + } + + // Test direct links/stream if episodes not tested or no episode links + if (directLinks.length > 0 && !result.stream.success) { + console.log(`\n 🎬 Found ${directLinks.length} direct link entries`); + + const testDirectLink = pickRandom(directLinks); + const linksToTest = pickRandom( + testDirectLink.link.directLinks, + Math.min(this.linksToTest, testDirectLink.link.directLinks.length) ); - results[module] = { - success: true, - size: response.data.length, - hasExports: response.data.includes("exports."), - }; - console.log(` ✅ ${module}.js (${results[module].size} bytes)`); - } catch (error) { - results[module] = { - success: false, - error: error.response?.status === 404 ? "Not found" : error.message, - }; - const isOptional = module === "episodes"; - const icon = isOptional ? "⚠️ " : "❌"; + console.log( - ` ${icon} ${module}.js - ${results[module].error}${ - isOptional ? " (optional)" : "" - }` + ` 🎲 Testing ${linksToTest.length} random direct link(s)` ); + + const streamModule = this.loadModule(providerName, "stream"); + if (streamModule && streamModule.getStream) { + for (const directLink of Array.isArray(linksToTest) + ? linksToTest + : [linksToTest]) { + console.log(`\n Testing: ${directLink.title}`); + console.log(` Link: ${directLink.link}`); + + try { + await sleep(500); + const streams = await streamModule.getStream({ + link: directLink.link, + type: directLink.type || "movie", + signal: this.signal, + providerContext, + }); + + if (Array.isArray(streams) && streams.length > 0) { + result.stream.success = true; + result.stream.data = { + count: streams.length, + type: directLink.type || "movie", + }; + console.log(` ✅ Got ${streams.length} stream(s)`); + streams.forEach((s, i) => { + console.log( + ` [${i + 1}] ${s.server} - ${ + s.quality || "unknown" + } quality` + ); + }); + break; // One success is enough + } else { + console.log(` ⚠️ No streams returned`); + } + } catch (err) { + console.log(` ❌ Stream error: ${err.message}`); + result.stream.error = err.message; + } + } + } else { + console.log(` ❌ getStream function not found`); + result.stream.error = "Function not available"; + } + } else if ( + !result.stream.success && + directLinks.length === 0 && + episodeLinks.length === 0 + ) { + result.stream.skipped = true; + console.log(` ℹ️ No links to test stream with`); + } + } catch (error) { + console.log(`\n❌ Test failed: ${error.message}`); + + // Determine which step failed + if (!result.catalog.success) { + result.catalog.error = error.message; + } else if (!result.posts.success) { + result.posts.error = error.message; + } else if (!result.meta.success) { + result.meta.error = error.message; } } - return results; + // Calculate summary + const steps = ["catalog", "posts", "meta", "episodes", "stream"]; + for (const step of steps) { + if (result[step].success) { + result.summary.passed++; + } else if (result[step].skipped) { + result.summary.skipped++; + } else if (result[step].error) { + result.summary.failed++; + } + } + + // Print summary + console.log(`\n${"─".repeat(60)}`); + console.log(`📊 Provider Summary: ${providerName}`); + console.log("─".repeat(60)); + console.log(` ✅ Passed: ${result.summary.passed}`); + console.log(` ❌ Failed: ${result.summary.failed}`); + console.log(` ⏭️ Skipped: ${result.summary.skipped}`); + + // List which steps passed/failed/skipped + console.log("\n Step Results:"); + for (const step of steps) { + if (result[step].success) { + console.log(` ✅ ${step}`); + } else if (result[step].skipped) { + console.log(` ⏭️ ${step} (skipped)`); + } else if (result[step].error) { + console.log(` ❌ ${step}: ${result[step].error}`); + } else { + console.log(` ⚪ ${step} (not tested)`); + } + } + + const statusIcon = result.summary.failed === 0 ? "✅" : "❌"; + console.log( + `\n ${statusIcon} Overall: ${ + result.summary.failed === 0 ? "PASSED" : "FAILED" + }` + ); + + return result; } /** * Test all providers */ async testAllProviders() { - console.log("🚀 Starting comprehensive provider test...\n"); + console.log("🚀 Starting comprehensive provider tests...\n"); - // Test connection - const connected = await this.testConnection(); - if (!connected) return; - - // Test manifest - const providers = await this.testManifest(); - if (!providers) return; - - // Test each provider - const results = {}; - for (const provider of providers) { - results[provider.value] = await this.testProvider(provider.value); - } - - // Summary - console.log("\n📊 Test Summary:"); - console.log("=".repeat(50)); - - let totalProviders = 0; - let passedProviders = 0; - - for (const [providerName, modules] of Object.entries(results)) { - totalProviders++; - const requiredModules = ["catalog", "posts", "meta", "stream"]; - const passedRequired = requiredModules.every( - (mod) => modules[mod]?.success - ); - - if (passedRequired) { - passedProviders++; - console.log(`✅ ${providerName} - All required modules OK`); - } else { - console.log(`❌ ${providerName} - Missing required modules`); - } - } - - console.log( - `\n📈 Results: ${passedProviders}/${totalProviders} providers passed` - ); - - if (passedProviders === totalProviders) { - console.log("🎉 All providers are ready for testing!"); - } else { - console.log("⚠️ Some providers need attention before testing."); - } - - return results; - } - - /** - * Trigger rebuild on server - */ - async rebuild() { - try { - console.log("🔨 Triggering rebuild..."); - const response = await this.axios.post("/build"); - console.log("✅ Rebuild completed"); - return true; - } catch (error) { - console.error( - "❌ Rebuild failed:", - error.response?.data?.error || error.message - ); - return false; - } - } - - /** - * Get server status - */ - async getStatus() { - try { - const response = await this.axios.get("/status"); - const status = response.data; - - console.log("📊 Server Status:"); - console.log(` 🟢 Status: ${status.status}`); - console.log(` 🔌 Port: ${status.port}`); - console.log(` 📦 Providers: ${status.providers}`); - console.log(` 🕐 Last Build: ${status.buildTime || "Never"}`); - - if (status.providerList.length > 0) { - console.log(" 📋 Available Providers:"); - status.providerList.forEach((p) => console.log(` • ${p}`)); - } - - return status; - } catch (error) { - console.error("❌ Failed to get status:", error.message); + if (!providerContext) { + console.log("❌ Provider context not loaded. Run 'npm run build' first."); return null; } + + const providers = this.getAvailableProviders(); + if (providers.length === 0) { + console.log("❌ No providers found."); + return null; + } + + console.log(`📦 Found ${providers.length} providers to test:`); + providers.forEach((p) => console.log(` • ${p}`)); + + const results = {}; + let passed = 0; + let failed = 0; + + for (const provider of providers) { + try { + results[provider] = await this.testProvider(provider); + if (results[provider].summary.failed === 0) { + passed++; + } else { + failed++; + } + } catch (error) { + console.log( + `\n❌ Critical error testing ${provider}: ${error.message}` + ); + failed++; + results[provider] = { error: error.message }; + } + + // Small delay between providers + await sleep(1000); + } + + // Final summary + console.log(`\n${"═".repeat(60)}`); + console.log("📊 FINAL TEST SUMMARY"); + console.log("═".repeat(60)); + console.log(` Total Providers: ${providers.length}`); + console.log(` ✅ Passed: ${passed}`); + console.log(` ❌ Failed: ${failed}`); + + // List failed providers with details + if (failed > 0) { + console.log(`\n${"─".repeat(60)}`); + console.log("❌ FAILED PROVIDERS:"); + console.log("─".repeat(60)); + + for (const [name, result] of Object.entries(results)) { + if (result.error) { + // Critical error + console.log(`\n ❌ ${name}`); + console.log(` Error: ${result.error}`); + } else if (result.summary?.failed > 0) { + // Step failures + console.log(`\n ❌ ${name}`); + const steps = ["catalog", "posts", "meta", "episodes", "stream"]; + for (const step of steps) { + if (result[step]?.error && !result[step]?.skipped) { + console.log(` • ${step}: ${result[step].error}`); + } + } + } + } + } + + // List passed providers + if (passed > 0) { + console.log(`\n${"─".repeat(60)}`); + console.log("✅ PASSED PROVIDERS:"); + console.log("─".repeat(60)); + const passedProviders = Object.entries(results) + .filter(([_, result]) => result.summary?.failed === 0 && !result.error) + .map(([name]) => name); + console.log(` ${passedProviders.join(", ")}`); + } + + console.log(`\n${"═".repeat(60)}`); + + return results; } } @@ -191,53 +593,52 @@ class ProviderTester { */ async function main() { const args = process.argv.slice(2); - const command = args[0] || "test"; - const providerName = args[1]; + const providerName = args[0]; - const tester = new ProviderTester(); + // Check for options + const postsToTest = + parseInt(args.find((a) => a.startsWith("--posts="))?.split("=")[1]) || 2; + const linksToTest = + parseInt(args.find((a) => a.startsWith("--links="))?.split("=")[1]) || 2; - switch (command) { - case "test": - if (providerName) { - await tester.testProvider(providerName); - } else { - await tester.testAllProviders(); - } - break; + const tester = new ProviderTester({ postsToTest, linksToTest }); - case "status": - await tester.getStatus(); - break; + if (args.includes("--help") || args.includes("-h")) { + console.log(` +🎯 Vega Providers Integration Tester +===================================== - case "rebuild": - await tester.rebuild(); - break; +Usage: node test-providers.js [provider] [options] - case "connection": - await tester.testConnection(); - break; +Arguments: + provider Name of specific provider to test (optional) + If not provided, tests all providers - case "manifest": - await tester.testManifest(); - break; +Options: + --posts=N Number of random posts to test (default: 2) + --links=N Number of random direct links to test (default: 2) + --help, -h Show this help message - default: - console.log(` -Usage: node test-providers.js [command] [provider] - -Commands: - test [provider] - Test all providers or specific provider - status - Show server status - rebuild - Trigger rebuild - connection - Test server connection - manifest - Test manifest endpoint +Test Flow: + 1. Load catalog → pick random filter + 2. Call getPosts with filter + 3. Pick random posts → call getMeta + 4. If episodesLink → call getEpisodes → getStream + 5. If directLinks → call getStream Examples: - node test-providers.js # Test all providers - node test-providers.js test vega # Test specific provider - node test-providers.js status # Show status - node test-providers.js rebuild # Rebuild and test - `); + node test-providers.js # Test all providers + node test-providers.js vega # Test only vega provider + node test-providers.js mod --posts=3 # Test mod with 3 random posts + node test-providers.js --posts=1 --links=1 # Quick test all providers + `); + return; + } + + if (providerName && !providerName.startsWith("--")) { + await tester.testProvider(providerName); + } else { + await tester.testAllProviders(); } }