rsshub
Version:
Make RSS Great Again!
524 lines (522 loc) • 18 kB
JavaScript
import { t as config } from "./config-CCmw1BNE.mjs";
import { i as registerRssRoutes, r as registerApiRoutes } from "./registry-helpers-DAzZYtjh.mjs";
import { t as isWorker } from "./is-worker-B5Qd1sOL.mjs";
import path from "node:path";
import { Hono } from "hono";
import { serveStatic } from "@hono/node-server/serve-static";
import { execSync } from "node:child_process";
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
import { PrometheusExporter, PrometheusSerializer } from "@opentelemetry/exporter-prometheus";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { MeterProvider } from "@opentelemetry/sdk-metrics";
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
import { trace } from "@opentelemetry/api";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { BasicTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
//#region lib/routes/healthz.ts
const handler$3 = (ctx) => {
ctx.header("Cache-Control", "no-cache");
return ctx.text("ok");
};
//#endregion
//#region lib/utils/debug-info.ts
const debug = {
hitCache: 0,
request: 0,
etag: 0,
error: 0,
routes: {},
paths: {},
errorRoutes: {},
errorPaths: {}
};
const getDebugInfo = () => debug;
const setDebugInfo = (info) => Object.assign(debug, info);
//#endregion
//#region lib/utils/git-hash.ts
let gitHash = process.env.HEROKU_SLUG_COMMIT?.slice(0, 8) || process.env.VERCEL_GIT_COMMIT_SHA?.slice(0, 8);
let gitDate;
if (!gitHash) try {
gitHash = execSync("git rev-parse HEAD").toString().trim().slice(0, 8);
gitDate = new Date(execSync("git log -1 --format=%cd").toString().trim());
} catch {
gitHash = "unknown";
}
//#endregion
//#region lib/views/layout.tsx
const Layout = (props) => /* @__PURE__ */ jsxs("html", { children: [/* @__PURE__ */ jsxs("head", { children: [
/* @__PURE__ */ jsx("title", { children: "Welcome to RSSHub!" }),
/* @__PURE__ */ jsx("meta", {
name: "color-scheme",
content: "light dark"
}),
/* @__PURE__ */ jsx("script", { src: "https://cdn.tailwindcss.com" }),
/* @__PURE__ */ jsx("style", { children: `
details::-webkit-scrollbar {
width: 0.25rem;
}
details::-webkit-scrollbar-thumb {
border-radius: 0.125rem;
background-color: #e4e4e7;
}
details::-webkit-scrollbar-thumb:hover {
background-color: #a1a1aa;
}
@media (prefers-color-scheme: dark) {
details::-webkit-scrollbar-thumb {
background-color: #3f3f46;
}
details::-webkit-scrollbar-thumb:hover {
background-color: #52525b;
}
}
@font-face {
font-family: SN Pro;
font-style: normal;
font-display: swap;
font-weight: 400;
src: url(https://cdn.jsdelivr.net/fontsource/fonts/sn-pro@latest/latin-400-normal.woff2) format(woff2);
}
@font-face {
font-family: SN Pro;
font-style: normal;
font-display: swap;
font-weight: 500;
src: url(https://cdn.jsdelivr.net/fontsource/fonts/sn-pro@latest/latin-500-normal.woff2) format(woff2);
}
@font-face {
font-family: SN Pro;
font-style: normal;
font-display: swap;
font-weight: 700;
src: url(https://cdn.jsdelivr.net/fontsource/fonts/sn-pro@latest/latin-700-normal.woff2) format(woff2);
}
body {
font-family: SN Pro, sans-serif;
}
`.replaceAll(/\s+/g, " ").trim() })
] }), /* @__PURE__ */ jsx("body", {
className: "antialiased min-h-screen text-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 flex flex-col",
children: props.children
})] });
//#endregion
//#region lib/views/index.tsx
const startTime = Date.now();
const Index = ({ debugQuery }) => {
const debug = getDebugInfo();
const showDebug = !config.debugInfo || config.debugInfo === "false" ? false : config.debugInfo === "true" || config.debugInfo === debugQuery;
const { disallowRobot, nodeName, cache } = config;
const duration = Date.now() - startTime;
const info = {
showDebug,
disallowRobot,
debug: [
...nodeName ? [{
name: "Node Name",
value: nodeName
}] : [],
...gitHash ? [{
name: "Git Hash",
value: /* @__PURE__ */ jsx("a", {
className: "underline",
href: `https://github.com/DIYgod/RSSHub/commit/${gitHash}`,
children: gitHash
})
}] : [],
...gitDate ? [{
name: "Git Date",
value: gitDate.toUTCString()
}] : [],
{
name: "Cache Duration",
value: cache.routeExpire + "s"
},
{
name: "Request Amount",
value: debug.request
},
{
name: "Request Frequency",
value: (debug.request / (duration / 1e3) * 60).toFixed(3) + " times/minute"
},
{
name: "Cache Hit Ratio",
value: debug.request ? (debug.hitCache / debug.request * 100).toFixed(2) + "%" : 0
},
{
name: "ETag Matched Ratio",
value: debug.request ? (debug.etag / debug.request * 100).toFixed(2) + "%" : 0
},
{
name: "Health",
value: debug.request ? ((1 - debug.error / debug.request) * 100).toFixed(2) + "%" : 0
},
{
name: "Uptime",
value: (duration / 36e5).toFixed(2) + " hour(s)"
},
{
name: "Hot Routes",
value: Object.keys(debug.routes).toSorted((a, b) => debug.routes[b] - debug.routes[a]).slice(0, 30).map((route) => /* @__PURE__ */ jsxs(Fragment, { children: [
debug.routes[route],
" ",
route,
/* @__PURE__ */ jsx("br", {})
] }))
},
{
name: "Hot Paths",
value: Object.keys(debug.paths).toSorted((a, b) => debug.paths[b] - debug.paths[a]).slice(0, 30).map((path) => /* @__PURE__ */ jsxs(Fragment, { children: [
debug.paths[path],
" ",
path,
/* @__PURE__ */ jsx("br", {})
] }))
},
{
name: "Hot Error Routes",
value: Object.keys(debug.errorRoutes).toSorted((a, b) => debug.errorRoutes[b] - debug.errorRoutes[a]).slice(0, 30).map((route) => /* @__PURE__ */ jsxs(Fragment, { children: [
debug.errorRoutes[route],
" ",
route,
/* @__PURE__ */ jsx("br", {})
] }))
},
{
name: "Hot Error Paths",
value: Object.keys(debug.errorPaths).toSorted((a, b) => debug.errorPaths[b] - debug.errorPaths[a]).slice(0, 30).map((path) => /* @__PURE__ */ jsxs(Fragment, { children: [
debug.errorPaths[path],
" ",
path,
/* @__PURE__ */ jsx("br", {})
] }))
}
]
};
return /* @__PURE__ */ jsxs(Layout, { children: [
/* @__PURE__ */ jsx("div", {
className: "pointer-events-none absolute w-full min-h-screen dark:invert",
style: {
backgroundImage: `url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAzMiAzMicgd2lkdGg9JzMyJyBoZWlnaHQ9JzMyJyBmaWxsPSdub25lJyBzdHJva2U9J3JnYigxNSAyMyA0MiAvIDAuMDQpJz48cGF0aCBkPSdNMCAuNUgzMS41VjMyJy8+PC9zdmc+')`,
maskImage: "linear-gradient(transparent, black, transparent)"
}
}),
/* @__PURE__ */ jsxs("div", {
className: "w-full grow shrink-0 py-8 flex items-center justify-center flex-col space-y-4",
children: [
/* @__PURE__ */ jsx("img", {
src: "./logo.png",
alt: "RSSHub",
width: "100",
loading: "lazy"
}),
/* @__PURE__ */ jsxs("h1", {
className: "text-4xl font-bold",
children: [
"Welcome to ",
/* @__PURE__ */ jsx("span", {
className: "text-[#F5712C]",
children: "RSSHub"
}),
"!"
]
}),
/* @__PURE__ */ jsx("p", {
className: "text-xl font-medium text-zinc-600 dark:text-zinc-300",
children: "The world's largest RSS Network."
}),
/* @__PURE__ */ jsx("p", {
className: "text-zinc-500 dark:text-zinc-400",
children: "If you see this page, the RSSHub is successfully installed and working."
}),
/* @__PURE__ */ jsxs("p", {
className: "max-w-xl text-center text-zinc-500 dark:text-zinc-400",
children: [
"Pair your feeds with",
" ",
/* @__PURE__ */ jsx("a", {
target: "_blank",
href: "https://folo.is/",
className: "text-[#F5712C]",
children: "Folo"
}),
", an AI RSS reader built for feed discovery and modern reading workflows. Source code is available on",
" ",
/* @__PURE__ */ jsx("a", {
target: "_blank",
href: "https://github.com/RSSNext/Folo",
className: "text-[#F5712C]",
children: "GitHub"
}),
"."
]
}),
/* @__PURE__ */ jsxs("div", {
className: "font-bold space-x-4 text-sm",
children: [
/* @__PURE__ */ jsx("a", {
target: "_blank",
href: "https://docs.rsshub.app",
children: /* @__PURE__ */ jsx("button", {
className: "text-white bg-[#F5712C] hover:bg-[#DD4A15] py-2 px-4 rounded-full transition-colors",
children: "Home"
})
}),
/* @__PURE__ */ jsx("a", {
target: "_blank",
href: "https://folo.is/",
children: /* @__PURE__ */ jsx("button", {
className: "bg-zinc-200 hover:bg-zinc-300 dark:bg-zinc-700 dark:hover:bg-zinc-600 py-2 px-4 rounded-full transition-colors",
children: "Try Folo"
})
}),
/* @__PURE__ */ jsx("a", {
target: "_blank",
href: "https://github.com/DIYgod/RSSHub",
children: /* @__PURE__ */ jsx("button", {
className: "bg-zinc-200 hover:bg-zinc-300 dark:bg-zinc-700 dark:hover:bg-zinc-600 py-2 px-4 rounded-full transition-colors",
children: "GitHub"
})
})
]
}),
info.showDebug ? /* @__PURE__ */ jsxs("details", {
className: "text-xs w-96 !mt-8 max-h-[400px] overflow-auto",
children: [/* @__PURE__ */ jsx("summary", {
className: "text-sm cursor-pointer",
children: "Debug Info"
}), info.debug.map((item) => /* @__PURE__ */ jsxs("div", {
class: "debug-item my-3 pl-8",
children: [/* @__PURE__ */ jsxs("span", {
class: "debug-key w-32 text-right inline-block mr-2",
children: [item.name, ": "]
}), /* @__PURE__ */ jsx("span", {
class: "debug-value inline-block break-all align-top",
children: item.value
})]
}))]
}) : null
]
}),
/* @__PURE__ */ jsxs("div", {
className: "text-center pt-4 pb-8 w-full text-sm font-medium space-y-2",
children: [
/* @__PURE__ */ jsxs("p", {
className: "space-x-4",
children: [
/* @__PURE__ */ jsx("a", {
target: "_blank",
href: "https://github.com/DIYgod/RSSHub",
children: /* @__PURE__ */ jsxs("picture", { children: [/* @__PURE__ */ jsx("source", {
srcset: "https://icons.ly/github/_/fff",
media: "(prefers-color-scheme: dark)"
}), /* @__PURE__ */ jsx("img", {
className: "inline",
src: "https://icons.ly/github",
alt: "github",
width: "20",
height: "20"
})] })
}),
/* @__PURE__ */ jsx("a", {
target: "_blank",
href: "https://t.me/rsshub",
children: /* @__PURE__ */ jsx("img", {
className: "inline",
src: "https://icons.ly/telegram",
alt: "telegram group",
width: "20",
height: "20"
})
}),
/* @__PURE__ */ jsx("a", {
target: "_blank",
href: "https://t.me/awesomeRSSHub",
children: /* @__PURE__ */ jsx("img", {
className: "inline",
src: "https://icons.ly/telegram",
alt: "telegram channel",
width: "20",
height: "20"
})
}),
/* @__PURE__ */ jsx("a", {
target: "_blank",
href: "https://x.com/intent/follow?screen_name=_RSSHub",
className: "text-[#F5712C]",
children: /* @__PURE__ */ jsxs("picture", { children: [/* @__PURE__ */ jsx("source", {
srcset: "https://icons.ly/x/_/fff",
media: "(prefers-color-scheme: dark)"
}), /* @__PURE__ */ jsx("img", {
className: "inline",
src: "https://icons.ly/x",
alt: "X",
width: "20",
height: "20"
})] })
})
]
}),
/* @__PURE__ */ jsxs("p", {
className: "!mt-6",
children: [
"Please consider",
" ",
/* @__PURE__ */ jsx("a", {
target: "_blank",
href: "https://docs.rsshub.app/sponsor",
className: "text-[#F5712C]",
children: "sponsoring"
}),
" ",
"to help keep this open source project alive."
]
}),
/* @__PURE__ */ jsxs("p", { children: [
"Made with ❤️ by",
" ",
/* @__PURE__ */ jsx("a", {
target: "_blank",
href: "https://diygod.cc",
className: "text-[#F5712C]",
children: "DIYgod"
}),
" ",
"and",
" ",
/* @__PURE__ */ jsx("a", {
target: "_blank",
href: "https://github.com/DIYgod/RSSHub/graphs/contributors",
className: "text-[#F5712C]",
children: "Contributors"
}),
" ",
"under AGPL-3.0 License."
] })
]
})
] });
};
//#endregion
//#region lib/routes/index.tsx
const handler$2 = (ctx) => {
ctx.header("Cache-Control", "no-cache");
return ctx.html(/* @__PURE__ */ jsx(Index, { debugQuery: ctx.req.query("debug") }));
};
//#endregion
//#region lib/utils/otel/metric.ts
const METRIC_PREFIX = "rsshub";
const exporter$1 = new PrometheusExporter({});
const provider$1 = new MeterProvider({
resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: "rsshub" }),
readers: [exporter$1]
});
const serializer = new PrometheusSerializer();
const meter = provider$1.getMeter("rsshub");
const requestTotal = meter.createCounter(`${METRIC_PREFIX}_request_total`);
const requestErrorTotal = meter.createCounter(`${METRIC_PREFIX}_request_error_total`);
const requestDurationSecondsBucket = meter.createHistogram(`${METRIC_PREFIX}_request_duration_seconds_bucket`, { advice: { explicitBucketBoundaries: config.otel.seconds_bucket?.split(",").map(Number) } });
const request_duration_milliseconds_bucket = meter.createHistogram(`${METRIC_PREFIX}_request_duration_milliseconds_bucket`, { advice: { explicitBucketBoundaries: config.otel.milliseconds_bucket?.split(",").map(Number) } });
const requestMetric = {
success: (value, attributes) => {
requestTotal.add(1, attributes);
request_duration_milliseconds_bucket.record(value, {
unit: "millisecond",
...attributes
});
requestDurationSecondsBucket.record(value / 1e3, {
unit: "second",
...attributes
});
},
error: (attributes) => {
requestErrorTotal.add(1, attributes);
}
};
const getContext = async () => {
try {
const value = await exporter$1.collect();
return serializer.serialize(value.resourceMetrics);
} catch {
throw "";
}
};
//#endregion
//#region lib/utils/otel/trace.ts
const exporter = new OTLPTraceExporter({});
const provider = new BasicTracerProvider({
resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: "rsshub" }),
spanProcessors: [new BatchSpanProcessor(exporter, {
maxQueueSize: 4096,
scheduledDelayMillis: 3e4
})]
});
trace.setGlobalTracerProvider(provider);
const tracer = provider.getTracer("rsshub");
tracer.startSpan("main");
//#endregion
//#region lib/routes/metrics.ts
const handler$1 = async (ctx) => {
try {
const val = await getContext();
return ctx.text(val);
} catch (error) {
ctx.status(500);
ctx.json({ error });
}
};
//#endregion
//#region lib/routes/robots.txt.ts
const handler = (ctx) => {
if (config.disallowRobot) return ctx.text("User-agent: *\nDisallow: /");
ctx.status(404);
return ctx.text("");
};
//#endregion
//#region lib/registry.ts
const __dirname = import.meta.dirname;
function isSafeRoutes(routes) {
return Object.values(routes).every((route) => !route.features?.nsfw);
}
function safeNamespaces(namespaces) {
const safe = {};
for (const [key, value] of Object.entries(namespaces)) if (value.routes === null || value.routes === void 0 || isSafeRoutes(value.routes)) safe[key] = value;
return safe;
}
let namespaces = {};
let devRegistry;
if (config.isPackage) namespaces = (await import("./routes-DVTE1YKY.mjs")).default;
else switch (process.env.NODE_ENV || process.env.VERCEL_ENV) {
case "production":
namespaces = (await import("./routes-DVTE1YKY.mjs")).default;
break;
case "test":
namespaces = await import("./routes-Dd-u1dsF.mjs");
if (namespaces.default) namespaces = namespaces.default;
break;
default: {
const { createDevRegistry } = await import("./registry-dev-0gyT2AUw.mjs");
devRegistry = createDevRegistry({
routesDirectory: path.join(__dirname, "./routes"),
namespaces
});
}
}
if (config.feature.disable_nsfw && !devRegistry) namespaces = safeNamespaces(namespaces);
const ensureAllLoaded = devRegistry?.ensureAllLoaded ?? (() => Promise.resolve());
const app = new Hono();
if (!devRegistry) {
registerRssRoutes(app, namespaces);
registerApiRoutes(app, namespaces);
}
app.get("/", handler$2);
app.get("/healthz", handler$3);
app.get("/robots.txt", handler);
if (config.debugInfo !== "false") app.get("/metrics", handler$1);
if (devRegistry) app.use("*", devRegistry.middleware);
if (!config.isPackage && !process.env.VERCEL_ENV && !isWorker) app.use("/*", serveStatic({
root: path.join(__dirname, "assets"),
rewriteRequestPath: (path) => path === "/favicon.ico" ? "/favicon.png" : path
}));
//#endregion
export { requestMetric as a, gitHash as c, tracer as i, getDebugInfo as l, ensureAllLoaded as n, Layout as o, namespaces as r, gitDate as s, app as t, setDebugInfo as u };