import fs from 'node:fs'; import path from 'node:path'; import { config } from '../../lib/config'; import { namespaces } from '../../lib/registry'; import { getCurrentPath } from '../../lib/utils/helpers'; import { categories } from './data'; const fullTests = await (await fetch('https://cdn.jsdelivr.net/gh/DIYgod/RSSHub@gh-pages/build/test-full-routes.json')).json(); const testResult = fullTests.testResults[0].assertionResults; const foloAnalysis = await ( await fetch('https://raw.githubusercontent.com/RSSNext/rsshub-docs/refs/heads/main/rsshub-analytics.json', { headers: { 'user-agent': config.trueUA, }, }) ).json(); const foloAnalysisResult = foloAnalysis.data as Record; const foloAnalysisTop100 = Object.entries(foloAnalysisResult) .sort((a, b) => b[1].subscriptionCount - a[1].subscriptionCount) .slice(0, 150); const __dirname = getCurrentPath(import.meta.url); // Build namespace-centric data structure interface NamespaceData { name: string; url?: string; description?: string; zh?: { name?: string; description?: string; }; categories: string[]; heat: number; routes: Record; } interface RouteData { path: string | string[]; name: string; url?: string; maintainers: string[]; example: string; parameters?: Record; description?: string; categories?: string[]; features?: Record; radar?: any[]; view?: number; location?: string; heat: number; topFeeds: any[]; zh?: { name?: string; description?: string; parameters?: Record; }; test?: { code: number; message?: string; }; } const namespacesData: Record = {}; // First pass: build namespace data with all routes for (const namespace in namespaces) { const nsData = namespaces[namespace]; // Determine default category let defaultCategory = nsData.categories?.[0]; if (!defaultCategory) { for (const routePath in nsData.routes) { if (nsData.routes[routePath].categories) { defaultCategory = nsData.routes[routePath].categories[0]; break; } } } if (!defaultCategory) { defaultCategory = 'other'; } // Collect all categories for this namespace const nsCategories = new Set(nsData.categories || []); // Initialize namespace data namespacesData[namespace] = { name: nsData.name === 'Unknown' ? namespace : nsData.name, url: nsData.url, description: nsData.description, zh: nsData.zh ? { name: nsData.zh.name, description: nsData.zh.description, } : undefined, categories: [], heat: 0, routes: {}, }; // Process routes const processedPaths = new Set(); for (const routePath in nsData.routes) { const realPath = `/${namespace}${routePath}`; const routeData = nsData.routes[routePath]; // Skip duplicate array paths if (Array.isArray(routeData.path)) { if (processedPaths.has(routeData.path[0])) { continue; } processedPaths.add(routeData.path[0]); } // Determine route categories const routeCategories = routeData.categories || nsData.categories || [defaultCategory]; // Check if popular const isPopular = foloAnalysisTop100.some(([p]) => p === realPath); if (isPopular && !routeCategories.includes('popular')) { routeCategories.push('popular'); } // Add categories to namespace for (const cat of routeCategories) { nsCategories.add(cat); } // Get test result const test = testResult.find((t) => t.title === realPath); const parsedTest = test ? { code: test.status === 'passed' ? 0 : 1, message: test.failureMessages?.[0], } : undefined; const heat = foloAnalysisResult[realPath]?.subscriptionCount || 0; // Build route data namespacesData[namespace].routes[realPath] = { path: routeData.path, name: routeData.name, url: routeData.url, maintainers: routeData.maintainers, example: routeData.example, parameters: routeData.parameters, description: routeData.description, categories: routeCategories, features: routeData.features, radar: routeData.radar, view: routeData.view, location: routeData.location, heat, topFeeds: foloAnalysisResult[realPath]?.topFeeds || [], zh: routeData.zh ? { name: routeData.zh.name, description: routeData.zh.description, parameters: routeData.zh.parameters, } : undefined, test: parsedTest, }; namespacesData[namespace].heat += heat; } namespacesData[namespace].categories = [...nsCategories]; } // Generate JSON output fs.mkdirSync(path.join(__dirname, '../../assets/build/docs'), { recursive: true }); fs.writeFileSync(path.join(__dirname, '../../assets/build/docs/routes.json'), JSON.stringify(namespacesData, null, 2)); // Generate categories data const categoriesData = categories.map((cat) => ({ id: cat.link.replace('/routes/', ''), icon: cat.icon, en: cat.en, zh: cat.zh, })); fs.writeFileSync(path.join(__dirname, '../../assets/build/docs/categories.json'), JSON.stringify(categoriesData, null, 2));