mirror of
https://github.com/DIYgod/RSSHub.git
synced 2025-12-04 19:59:54 +08:00
feat: radar rules api
This commit is contained in:
7
lib/api/index.ts
Normal file
7
lib/api/index.ts
Normal file
@@ -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;
|
||||||
41
lib/api/radar/rules.ts
Normal file
41
lib/api/radar/rules.ts
Normal file
@@ -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;
|
||||||
@@ -17,6 +17,7 @@ import logger from '@/utils/logger';
|
|||||||
|
|
||||||
import { notFoundHandler, errorHandler } from '@/errors';
|
import { notFoundHandler, errorHandler } from '@/errors';
|
||||||
import registry from '@/registry';
|
import registry from '@/registry';
|
||||||
|
import api from '@/api';
|
||||||
|
|
||||||
process.on('uncaughtException', (e) => {
|
process.on('uncaughtException', (e) => {
|
||||||
logger.error('uncaughtException: ' + e);
|
logger.error('uncaughtException: ' + e);
|
||||||
@@ -36,7 +37,8 @@ app.use(antiHotlink);
|
|||||||
app.use(parameter);
|
app.use(parameter);
|
||||||
app.use(cache);
|
app.use(cache);
|
||||||
|
|
||||||
registry(app);
|
app.route('/', registry);
|
||||||
|
app.route('/api', api);
|
||||||
|
|
||||||
app.notFound(notFoundHandler);
|
app.notFound(notFoundHandler);
|
||||||
app.onError(errorHandler);
|
app.onError(errorHandler);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Namespace, Route } from '@/types';
|
import type { Namespace, Route } from '@/types';
|
||||||
import { directoryImport } from 'directory-import';
|
import { directoryImport } from 'directory-import';
|
||||||
import type { Hono, Handler } from 'hono';
|
import { Hono, type Handler } from 'hono';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { serveStatic } from '@hono/node-server/serve-static';
|
import { serveStatic } from '@hono/node-server/serve-static';
|
||||||
@@ -91,32 +91,31 @@ if (Object.keys(modules).length) {
|
|||||||
|
|
||||||
export { namespaces };
|
export { namespaces };
|
||||||
|
|
||||||
export default function (app: Hono) {
|
const app = new Hono();
|
||||||
for (const namespace in namespaces) {
|
for (const namespace in namespaces) {
|
||||||
const subApp = app.basePath(`/${namespace}`);
|
const subApp = app.basePath(`/${namespace}`);
|
||||||
for (const path in namespaces[namespace].routes) {
|
for (const path in namespaces[namespace].routes) {
|
||||||
const wrapedHandler: Handler = async (ctx) => {
|
const wrapedHandler: Handler = async (ctx) => {
|
||||||
if (!ctx.get('data')) {
|
if (!ctx.get('data')) {
|
||||||
if (typeof namespaces[namespace].routes[path].handler !== 'function') {
|
if (typeof namespaces[namespace].routes[path].handler !== 'function') {
|
||||||
const { route } = await import(`./routes/${namespace}/${namespaces[namespace].routes[path].location}`);
|
const { route } = await import(`./routes/${namespace}/${namespaces[namespace].routes[path].location}`);
|
||||||
namespaces[namespace].routes[path].handler = route.handler;
|
namespaces[namespace].routes[path].handler = route.handler;
|
||||||
}
|
|
||||||
ctx.set('data', await namespaces[namespace].routes[path].handler(ctx));
|
|
||||||
}
|
}
|
||||||
};
|
ctx.set('data', await namespaces[namespace].routes[path].handler(ctx));
|
||||||
subApp.get(path, wrapedHandler);
|
}
|
||||||
}
|
};
|
||||||
|
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;
|
||||||
|
|||||||
19
lib/types.ts
19
lib/types.ts
@@ -1,5 +1,6 @@
|
|||||||
import type { Context } from 'hono';
|
import type { Context } from 'hono';
|
||||||
|
|
||||||
|
// rss
|
||||||
export type DataItem = {
|
export type DataItem = {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@@ -46,6 +47,7 @@ export type Data = {
|
|||||||
lastBuildDate?: string;
|
lastBuildDate?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// namespace
|
||||||
interface NamespaceItem {
|
interface NamespaceItem {
|
||||||
name: string;
|
name: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
@@ -61,6 +63,7 @@ interface Namespace extends NamespaceItem {
|
|||||||
|
|
||||||
export type { Namespace };
|
export type { Namespace };
|
||||||
|
|
||||||
|
// route
|
||||||
interface RouteItem {
|
interface RouteItem {
|
||||||
path: string | string[];
|
path: string | string[];
|
||||||
name: string;
|
name: string;
|
||||||
@@ -100,3 +103,19 @@ interface Route extends RouteItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type { Route };
|
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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -109,6 +109,7 @@
|
|||||||
"telegram": "2.20.2",
|
"telegram": "2.20.2",
|
||||||
"tiny-async-pool": "2.1.0",
|
"tiny-async-pool": "2.1.0",
|
||||||
"title": "3.5.3",
|
"title": "3.5.3",
|
||||||
|
"tldts": "6.1.13",
|
||||||
"tough-cookie": "4.1.3",
|
"tough-cookie": "4.1.3",
|
||||||
"tsx": "4.7.1",
|
"tsx": "4.7.1",
|
||||||
"twitter-api-v2": "1.16.1",
|
"twitter-api-v2": "1.16.1",
|
||||||
@@ -172,7 +173,6 @@
|
|||||||
"staged-git-files": "1.3.0",
|
"staged-git-files": "1.3.0",
|
||||||
"string-width": "7.1.0",
|
"string-width": "7.1.0",
|
||||||
"supertest": "6.3.4",
|
"supertest": "6.3.4",
|
||||||
"tldts": "6.1.13",
|
|
||||||
"to-vfile": "8.0.0",
|
"to-vfile": "8.0.0",
|
||||||
"tosource": "2.0.0-alpha.3",
|
"tosource": "2.0.0-alpha.3",
|
||||||
"typescript": "5.4.2",
|
"typescript": "5.4.2",
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -194,6 +194,9 @@ dependencies:
|
|||||||
title:
|
title:
|
||||||
specifier: 3.5.3
|
specifier: 3.5.3
|
||||||
version: 3.5.3
|
version: 3.5.3
|
||||||
|
tldts:
|
||||||
|
specifier: 6.1.13
|
||||||
|
version: 6.1.13
|
||||||
tough-cookie:
|
tough-cookie:
|
||||||
specifier: 4.1.3
|
specifier: 4.1.3
|
||||||
version: 4.1.3
|
version: 4.1.3
|
||||||
@@ -379,9 +382,6 @@ devDependencies:
|
|||||||
supertest:
|
supertest:
|
||||||
specifier: 6.3.4
|
specifier: 6.3.4
|
||||||
version: 6.3.4
|
version: 6.3.4
|
||||||
tldts:
|
|
||||||
specifier: 6.1.13
|
|
||||||
version: 6.1.13
|
|
||||||
to-vfile:
|
to-vfile:
|
||||||
specifier: 8.0.0
|
specifier: 8.0.0
|
||||||
version: 8.0.0
|
version: 8.0.0
|
||||||
@@ -9442,14 +9442,14 @@ packages:
|
|||||||
|
|
||||||
/tldts-core@6.1.13:
|
/tldts-core@6.1.13:
|
||||||
resolution: {integrity: sha512-M1XP4D13YtXARKroULnLsKKuI1NCRAbJmUGGoXqWinajIDOhTeJf/trYUyBoLVx1/Nx1KBKxCrlW57ZW9cMHAA==}
|
resolution: {integrity: sha512-M1XP4D13YtXARKroULnLsKKuI1NCRAbJmUGGoXqWinajIDOhTeJf/trYUyBoLVx1/Nx1KBKxCrlW57ZW9cMHAA==}
|
||||||
dev: true
|
dev: false
|
||||||
|
|
||||||
/tldts@6.1.13:
|
/tldts@6.1.13:
|
||||||
resolution: {integrity: sha512-+GxHFKVHvUTg2ieNPTx3b/NpZbgJSTZEDdI4cJzTjVYDuxijeHi1tt7CHHsMjLqyc+T50VVgWs3LIb2LrXOzxw==}
|
resolution: {integrity: sha512-+GxHFKVHvUTg2ieNPTx3b/NpZbgJSTZEDdI4cJzTjVYDuxijeHi1tt7CHHsMjLqyc+T50VVgWs3LIb2LrXOzxw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
tldts-core: 6.1.13
|
tldts-core: 6.1.13
|
||||||
dev: true
|
dev: false
|
||||||
|
|
||||||
/tmp@0.0.33:
|
/tmp@0.0.33:
|
||||||
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
|
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
|
||||||
|
|||||||
@@ -1,23 +1,12 @@
|
|||||||
import { namespaces } from '../../lib/registry';
|
import { namespaces } from '../../lib/registry';
|
||||||
|
import { Radar } from '../../lib/types';
|
||||||
import { parse } from 'tldts';
|
import { parse } from 'tldts';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import toSource from 'tosource';
|
import toSource from 'tosource';
|
||||||
|
|
||||||
const maintainers: Record<string, string[]> = {};
|
const maintainers: Record<string, string[]> = {};
|
||||||
const radar: {
|
const radar: Radar = {};
|
||||||
[domain: string]: {
|
|
||||||
_name: string;
|
|
||||||
[subdomain: string]:
|
|
||||||
| {
|
|
||||||
title: string;
|
|
||||||
docs: string;
|
|
||||||
source: string[];
|
|
||||||
target: string | ((params: any, url: string) => string);
|
|
||||||
}[]
|
|
||||||
| string;
|
|
||||||
};
|
|
||||||
} = {};
|
|
||||||
const docs = {};
|
const docs = {};
|
||||||
|
|
||||||
for (const namespace in namespaces) {
|
for (const namespace in namespaces) {
|
||||||
|
|||||||
Reference in New Issue
Block a user