From 901075eda94e1bd624d3a30358c7b36738eb24d0 Mon Sep 17 00:00:00 2001 From: DIYgod Date: Thu, 14 Mar 2024 20:06:15 +0800 Subject: [PATCH] feat: radar rules api --- lib/api/index.ts | 7 +++++ lib/api/radar/rules.ts | 41 ++++++++++++++++++++++++ lib/app.ts | 4 ++- lib/registry.ts | 53 ++++++++++++++++---------------- lib/types.ts | 19 ++++++++++++ package.json | 2 +- pnpm-lock.yaml | 10 +++--- scripts/workflow/build-routes.ts | 15 ++------- 8 files changed, 104 insertions(+), 47 deletions(-) create mode 100644 lib/api/index.ts create mode 100644 lib/api/radar/rules.ts diff --git a/lib/api/index.ts b/lib/api/index.ts new file mode 100644 index 0000000000..febb9da9f1 --- /dev/null +++ b/lib/api/index.ts @@ -0,0 +1,7 @@ +import { Hono } from 'hono'; +import rules from '@/api/radar/rules'; + +const app = new Hono(); +app.get('/radar/rules.json', rules); + +export default app; diff --git a/lib/api/radar/rules.ts b/lib/api/radar/rules.ts new file mode 100644 index 0000000000..852c38c79b --- /dev/null +++ b/lib/api/radar/rules.ts @@ -0,0 +1,41 @@ +import type { Handler } from 'hono'; +import { namespaces } from '@/registry'; +import { parse } from 'tldts'; +import { Radar } from '@/types'; + +const radar: Radar = {}; + +for (const namespace in namespaces) { + for (const path in namespaces[namespace].routes) { + const realPath = `/${namespace}${path}`; + const data = namespaces[namespace].routes[path]; + if (data.radar && data.radar.source) { + const parsedDomain = parse(new URL('https://' + data.radar.source[0]).hostname); + const subdomain = parsedDomain.subdomain || '.'; + const domain = parsedDomain.domain; + if (domain) { + if (!radar[domain]) { + radar[domain] = { + _name: namespaces[namespace].name, + }; + } + if (!radar[domain][subdomain]) { + radar[domain][subdomain] = []; + } + radar[domain][subdomain].push({ + title: data.name, + docs: `https://docs.rsshub.app/routes/${data.categories?.[0] || 'other'}`, + source: data.radar.source.map((source) => { + const sourceURL = new URL('https://' + source); + return sourceURL.pathname + sourceURL.search + sourceURL.hash; + }), + target: data.radar.target || realPath, + }); + } + } + } +} + +const handler: Handler = (ctx) => ctx.json(radar); + +export default handler; diff --git a/lib/app.ts b/lib/app.ts index 782c938f3c..66d6db2750 100644 --- a/lib/app.ts +++ b/lib/app.ts @@ -17,6 +17,7 @@ import logger from '@/utils/logger'; import { notFoundHandler, errorHandler } from '@/errors'; import registry from '@/registry'; +import api from '@/api'; process.on('uncaughtException', (e) => { logger.error('uncaughtException: ' + e); @@ -36,7 +37,8 @@ app.use(antiHotlink); app.use(parameter); app.use(cache); -registry(app); +app.route('/', registry); +app.route('/api', api); app.notFound(notFoundHandler); app.onError(errorHandler); diff --git a/lib/registry.ts b/lib/registry.ts index cb5aef1bbc..0fef532aed 100644 --- a/lib/registry.ts +++ b/lib/registry.ts @@ -1,6 +1,6 @@ import type { Namespace, Route } from '@/types'; import { directoryImport } from 'directory-import'; -import type { Hono, Handler } from 'hono'; +import { Hono, type Handler } from 'hono'; import * as path from 'node:path'; import { fileURLToPath } from 'node:url'; import { serveStatic } from '@hono/node-server/serve-static'; @@ -91,32 +91,31 @@ if (Object.keys(modules).length) { export { namespaces }; -export default function (app: Hono) { - for (const namespace in namespaces) { - const subApp = app.basePath(`/${namespace}`); - for (const path in namespaces[namespace].routes) { - const wrapedHandler: Handler = async (ctx) => { - if (!ctx.get('data')) { - if (typeof namespaces[namespace].routes[path].handler !== 'function') { - const { route } = await import(`./routes/${namespace}/${namespaces[namespace].routes[path].location}`); - namespaces[namespace].routes[path].handler = route.handler; - } - ctx.set('data', await namespaces[namespace].routes[path].handler(ctx)); +const app = new Hono(); +for (const namespace in namespaces) { + const subApp = app.basePath(`/${namespace}`); + for (const path in namespaces[namespace].routes) { + const wrapedHandler: Handler = async (ctx) => { + if (!ctx.get('data')) { + if (typeof namespaces[namespace].routes[path].handler !== 'function') { + const { route } = await import(`./routes/${namespace}/${namespaces[namespace].routes[path].location}`); + namespaces[namespace].routes[path].handler = route.handler; } - }; - subApp.get(path, wrapedHandler); - } + ctx.set('data', await namespaces[namespace].routes[path].handler(ctx)); + } + }; + subApp.get(path, wrapedHandler); } - - // routes without rss data - app.get('/', index); - app.get('/robots.txt', robotstxt); - - app.use( - '/*', - serveStatic({ - root: './lib/assets', - rewriteRequestPath: (path) => (path === '/favicon.ico' ? '/favicon.png' : path), - }) - ); } + +app.get('/', index); +app.get('/robots.txt', robotstxt); +app.use( + '/*', + serveStatic({ + root: './lib/assets', + rewriteRequestPath: (path) => (path === '/favicon.ico' ? '/favicon.png' : path), + }) +); + +export default app; diff --git a/lib/types.ts b/lib/types.ts index f885cc6198..c134d7ecf8 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,5 +1,6 @@ import type { Context } from 'hono'; +// rss export type DataItem = { title: string; description?: string; @@ -46,6 +47,7 @@ export type Data = { lastBuildDate?: string; }; +// namespace interface NamespaceItem { name: string; url?: string; @@ -61,6 +63,7 @@ interface Namespace extends NamespaceItem { export type { Namespace }; +// route interface RouteItem { path: string | string[]; name: string; @@ -100,3 +103,19 @@ interface Route extends RouteItem { } export type { Route }; + +// radar +export type Radar = { + [domain: string]: + | { + [subdomain: string]: { + title: string; + docs: string; + source: string[]; + target: string | ((params: any, url: string) => string); + }[]; + } + | { + _name: string; + }; +}; diff --git a/package.json b/package.json index 2fc3d7c132..97953bb83f 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "telegram": "2.20.2", "tiny-async-pool": "2.1.0", "title": "3.5.3", + "tldts": "6.1.13", "tough-cookie": "4.1.3", "tsx": "4.7.1", "twitter-api-v2": "1.16.1", @@ -172,7 +173,6 @@ "staged-git-files": "1.3.0", "string-width": "7.1.0", "supertest": "6.3.4", - "tldts": "6.1.13", "to-vfile": "8.0.0", "tosource": "2.0.0-alpha.3", "typescript": "5.4.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4856ff6f2a..3cbea10d51 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -194,6 +194,9 @@ dependencies: title: specifier: 3.5.3 version: 3.5.3 + tldts: + specifier: 6.1.13 + version: 6.1.13 tough-cookie: specifier: 4.1.3 version: 4.1.3 @@ -379,9 +382,6 @@ devDependencies: supertest: specifier: 6.3.4 version: 6.3.4 - tldts: - specifier: 6.1.13 - version: 6.1.13 to-vfile: specifier: 8.0.0 version: 8.0.0 @@ -9442,14 +9442,14 @@ packages: /tldts-core@6.1.13: resolution: {integrity: sha512-M1XP4D13YtXARKroULnLsKKuI1NCRAbJmUGGoXqWinajIDOhTeJf/trYUyBoLVx1/Nx1KBKxCrlW57ZW9cMHAA==} - dev: true + dev: false /tldts@6.1.13: resolution: {integrity: sha512-+GxHFKVHvUTg2ieNPTx3b/NpZbgJSTZEDdI4cJzTjVYDuxijeHi1tt7CHHsMjLqyc+T50VVgWs3LIb2LrXOzxw==} hasBin: true dependencies: tldts-core: 6.1.13 - dev: true + dev: false /tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} diff --git a/scripts/workflow/build-routes.ts b/scripts/workflow/build-routes.ts index 89f3cc5cac..0a69604bd1 100644 --- a/scripts/workflow/build-routes.ts +++ b/scripts/workflow/build-routes.ts @@ -1,23 +1,12 @@ import { namespaces } from '../../lib/registry'; +import { Radar } from '../../lib/types'; import { parse } from 'tldts'; import fs from 'node:fs'; import * as path from 'node:path'; import toSource from 'tosource'; const maintainers: Record = {}; -const radar: { - [domain: string]: { - _name: string; - [subdomain: string]: - | { - title: string; - docs: string; - source: string[]; - target: string | ((params: any, url: string) => string); - }[] - | string; - }; -} = {}; +const radar: Radar = {}; const docs = {}; for (const namespace in namespaces) {