| | 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<string, { subscriptionCount: number; topFeeds: any[] }>; |
| | const foloAnalysisTop100 = Object.entries(foloAnalysisResult) |
| | .sort((a, b) => b[1].subscriptionCount - a[1].subscriptionCount) |
| | .slice(0, 150); |
| |
|
| | const __dirname = getCurrentPath(import.meta.url); |
| |
|
| | |
| | interface NamespaceData { |
| | name: string; |
| | url?: string; |
| | description?: string; |
| | zh?: { |
| | name?: string; |
| | description?: string; |
| | }; |
| | categories: string[]; |
| | heat: number; |
| | routes: Record<string, RouteData>; |
| | } |
| |
|
| | interface RouteData { |
| | path: string | string[]; |
| | name: string; |
| | url?: string; |
| | maintainers: string[]; |
| | example: string; |
| | parameters?: Record<string, any>; |
| | description?: string; |
| | categories?: string[]; |
| | features?: Record<string, any>; |
| | radar?: any[]; |
| | view?: number; |
| | location?: string; |
| | heat: number; |
| | topFeeds: any[]; |
| | zh?: { |
| | name?: string; |
| | description?: string; |
| | parameters?: Record<string, any>; |
| | }; |
| | test?: { |
| | code: number; |
| | message?: string; |
| | }; |
| | } |
| |
|
| | const namespacesData: Record<string, NamespaceData> = {}; |
| |
|
| | |
| | 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'; |
| | } |
| |
|
| | |
| | const nsCategories = new Set<string>(nsData.categories || []); |
| |
|
| | |
| | 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: {}, |
| | }; |
| |
|
| | |
| | const processedPaths = new Set<string>(); |
| |
|
| | for (const routePath in nsData.routes) { |
| | const realPath = `/${namespace}${routePath}`; |
| | const routeData = nsData.routes[routePath]; |
| |
|
| | |
| | if (Array.isArray(routeData.path)) { |
| | if (processedPaths.has(routeData.path[0])) { |
| | continue; |
| | } |
| | processedPaths.add(routeData.path[0]); |
| | } |
| |
|
| | |
| | const routeCategories = routeData.categories || nsData.categories || [defaultCategory]; |
| |
|
| | |
| | const isPopular = foloAnalysisTop100.some(([p]) => p === realPath); |
| | if (isPopular && !routeCategories.includes('popular')) { |
| | routeCategories.push('popular'); |
| | } |
| |
|
| | |
| | for (const cat of routeCategories) { |
| | nsCategories.add(cat); |
| | } |
| |
|
| | |
| | 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; |
| |
|
| | |
| | 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)); |
| |
|
| | |
| | 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)); |
| |
|