mirror of
https://github.com/vega-org/vega-providers.git
synced 2026-04-17 15:41:45 +00:00
640 lines
21 KiB
JavaScript
640 lines
21 KiB
JavaScript
const axios = require("axios");
|
||
const cheerio = require("cheerio");
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
|
||
// Load utilities
|
||
let providerContext;
|
||
try {
|
||
const { getBaseUrl } = require("./dist/getBaseUrl.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",
|
||
},
|
||
Aes: {},
|
||
};
|
||
} catch (error) {
|
||
console.log(
|
||
"⚠️ Could not load provider context. Run 'npm run build' first.",
|
||
);
|
||
providerContext = null;
|
||
}
|
||
|
||
/**
|
||
* 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(options = {}) {
|
||
this.timeout = options.timeout || 30000;
|
||
this.postsToTest = options.postsToTest || 2;
|
||
this.linksToTest = options.linksToTest || 2;
|
||
this.signal = new AbortController().signal;
|
||
this.results = {};
|
||
}
|
||
|
||
/**
|
||
* Load provider module
|
||
*/
|
||
loadModule(providerName, moduleName) {
|
||
try {
|
||
const modulePath = `./dist/${providerName}/${moduleName}.js`;
|
||
// Clear cache to get fresh module
|
||
delete require.cache[require.resolve(modulePath)];
|
||
return require(modulePath);
|
||
} catch (error) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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${"=".repeat(60)}`);
|
||
console.log(`🧪 Testing Provider: ${providerName}`);
|
||
console.log("=".repeat(60));
|
||
|
||
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 },
|
||
};
|
||
|
||
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),
|
||
);
|
||
|
||
console.log(
|
||
` 🎲 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;
|
||
}
|
||
}
|
||
|
||
// 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 tests...\n");
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* CLI interface
|
||
*/
|
||
async function main() {
|
||
const args = process.argv.slice(2);
|
||
const providerName = args[0];
|
||
|
||
// 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;
|
||
|
||
const tester = new ProviderTester({ postsToTest, linksToTest });
|
||
|
||
if (args.includes("--help") || args.includes("-h")) {
|
||
console.log(`
|
||
🎯 Vega Providers Integration Tester
|
||
=====================================
|
||
|
||
Usage: node test-providers.js [provider] [options]
|
||
|
||
Arguments:
|
||
provider Name of specific provider to test (optional)
|
||
If not provided, tests all providers
|
||
|
||
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
|
||
|
||
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 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();
|
||
}
|
||
}
|
||
|
||
if (require.main === module) {
|
||
main().catch(console.error);
|
||
}
|
||
|
||
module.exports = ProviderTester;
|