rsshub
Version:
Make RSS Great Again!
238 lines (234 loc) • 9.47 kB
JavaScript
import { t as parseDate } from "./parse-date-Cr0wQjV_.mjs";
import { t as got_default } from "./got-BTowW50G.mjs";
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
import { load } from "cheerio";
import { renderToString } from "hono/jsx/dom/server";
import { raw } from "hono/html";
//#region lib/routes/the/templates/description.tsx
const resolveImageAlt = (image) => image?.height ?? image?.width ?? image?.alt;
const renderDescription = ({ images, intro, description }) => renderToString(/* @__PURE__ */ jsxs(Fragment, { children: [
images?.length ? images.map((image) => image?.src ? /* @__PURE__ */ jsx("figure", { children: /* @__PURE__ */ jsx("img", {
src: image.src,
alt: resolveImageAlt(image)
}) }, image.src) : null) : null,
intro ? /* @__PURE__ */ jsx("blockquote", { children: intro }) : null,
description ? /* @__PURE__ */ jsx(Fragment, { children: raw(description) }) : null
] }));
//#endregion
//#region lib/routes/the/index.ts
const baseUrl = "https://river.to/occasus";
const handler = async (ctx) => {
const { filter } = ctx.req.param();
const limit = ctx.req.query("limit") ? Number(ctx.req.query("limit")) : 40;
const slug = parseSlug(filter);
const targetUrl = slug ? `${baseUrl}/${slug}` : `${baseUrl}/`;
const { data: response } = await got_default(targetUrl);
const $ = load(response);
const title = $("title").text() || "江河日下";
const items = [...extractFromMaterialBlocks($, targetUrl), ...extractFromPostPreviews($, targetUrl)];
return {
title: `${title} - 江河日下`,
description: $("meta[property=\"og:description\"]").attr("content") || $("meta[name=\"description\"]").attr("content"),
link: targetUrl,
allowEmpty: true,
image: $("meta[property=\"og:image\"]").attr("content") || "",
language: "zh-CN",
item: items.slice(0, limit)
};
};
/**
* Parse filter string to get the target slug.
* Supports: category/slug, tag/slug, or just slug directly.
*/
function parseSlug(filter) {
if (!filter) return;
const parts = filter.split("/").filter(Boolean);
if (parts.length >= 2 && (parts[0] === "category" || parts[0] === "tag")) return parts[1];
if (parts.length === 1) return parts[0];
return parts[1] || parts[0];
}
/**
* Find the first real article link in an element.
* Reason: only links inside h3/h4 (article title) or wrapping the main image are real article links.
* Links in author/publication areas (.postMetaInline, .uiScale, avatar containers) are excluded.
*/
function findArticleLink($, block) {
let link;
block.find("a").each((_, el) => {
const $el = $(el);
const href = $el.attr("href");
if (!href || href === "#Unauthorized" || href === baseUrl || href === `${baseUrl}/` || !href.startsWith("https://river.to/occasus/")) return;
const hasTitle = $el.find("h3").length > 0;
const isMainImage = $el.hasClass("u-block") && $el.find(".graf-image").length > 0;
if (hasTitle || isMainImage) {
link = href;
return false;
}
});
return link;
}
/**
* Extract articles from material5* blocks (featured cards on category/publication pages).
* Reason: on category pages, article links are often #Unauthorized (subscriber-only).
* We still extract the metadata and use the page URL + title hash as guid.
*/
function extractFromMaterialBlocks($, pageUrl) {
return $("[class^=\"material5\"], [class^=\"material3\"], [class^=\"material2\"]").toArray().map((el) => {
const block = $(el);
const title = block.find("h3").text().trim();
const summary = block.find(".ui-summary, h4.ui-summary, p.ui-summary").text().trim();
const dateStr = block.find("time").attr("datetime");
const tag = block.find(".TagName").attr("title");
const author = block.find(".postMetaInline--author").text().trim();
const image = block.find("img.graf-image--src").attr("data-srcset");
const articleLink = findArticleLink($, block);
const link = articleLink || pageUrl;
const guid = articleLink || `${pageUrl}#${encodeURIComponent(title)}`;
return {
title,
description: renderDescription({
images: image ? [{ src: image }] : void 0,
intro: summary || void 0
}),
pubDate: dateStr ? parseDate(dateStr) : void 0,
link,
category: tag ? [tag] : [],
author,
guid
};
});
}
/**
* Extract articles from material6Lite streamItem--postPreview blocks.
* Reason: some category pages (e.g. Beijing) use this list format instead of material5 or extremePostPreview.
* These are typically subscriber-only articles with #Unauthorized links.
*/
function extractFromPostPreviews($, pageUrl) {
return $(".streamItem--postPreview").toArray().map((el) => {
const block = $(el);
const title = block.find("h3.graf--title").text().trim();
const summary = block.find("h4.ui-summary, .graf--subtitle").text().trim();
const dateStr = block.find("time").attr("datetime");
const author = block.find(".postMetaInline--author").text().trim();
const tag = block.find(".TagName").attr("title");
const image = block.find("img.graf-image--src").not(".avatar-image img").attr("data-srcset");
const articleLink = findArticleLink($, block);
const link = articleLink || pageUrl;
const guid = articleLink || `${pageUrl}#${encodeURIComponent(title)}`;
return {
title,
description: renderDescription({
images: image ? [{ src: image }] : void 0,
intro: summary || void 0
}),
pubDate: dateStr ? parseDate(dateStr) : void 0,
link,
category: tag ? [tag] : [],
author,
guid
};
});
}
const route = {
path: "/:filter{.+}?",
name: "分类",
url: "river.to",
maintainers: ["nczitzk", "pseudoyu"],
handler,
example: "/the",
parameters: { filter: "过滤器,见下方描述" },
description: `::: tip
如果你想订阅特定类别或出版物,可以在路由中填写 filter 参数。
\`/category/rawj7o4ypewv94\` 可以实现订阅 [时局](https://river.to/occasus/rawj7o4ypewv94) 类别。此时,路由是 [\`/the/category/rawj7o4ypewv94/\`](https://rsshub.app/the/category/rawj7o4ypewv94).
也可以直接使用 slug:[\`/the/rawj7o4ypewv94\`](https://rsshub.app/the/rawj7o4ypewv94)
:::
| 分类 | ID |
| --------------------------------------------------- | ---------------------------------------------------------------- |
| [时局](https://river.to/occasus/rawj7o4ypewv94) | [rawj7o4ypewv94](https://rsshub.app/the/category/rawj7o4ypewv94) |
| [剩余价值](https://river.to/occasus/rawmw7dsta2jew) | [rawmw7dsta2jew](https://rsshub.app/the/category/rawmw7dsta2jew) |
| [Beijing](https://river.to/occasus/rawbcvxkktdkq8) | [rawbcvxkktdkq8](https://rsshub.app/the/category/rawbcvxkktdkq8) |
| [稳中向好](https://river.to/occasus/raw4krvx85dh27) | [raw4krvx85dh27](https://rsshub.app/the/category/raw4krvx85dh27) |
| [水深火热](https://river.to/occasus/rawtn8jpsc6uvv) | [rawtn8jpsc6uvv](https://rsshub.app/the/category/rawtn8jpsc6uvv) |
| [东升西降](https://river.to/occasus/rawai5kd4z15il) | [rawai5kd4z15il](https://rsshub.app/the/category/rawai5kd4z15il) |
| [大局](https://river.to/occasus/raw2efkzejrsx8) | [raw2efkzejrsx8](https://rsshub.app/the/category/raw2efkzejrsx8) |
| [境外势力](https://river.to/occasus/rawmpalhnlphuc) | [rawmpalhnlphuc](https://rsshub.app/the/category/rawmpalhnlphuc) |
| [副刊](https://river.to/occasus/rawxght2jr2u5z) | [rawxght2jr2u5z](https://rsshub.app/the/category/rawxght2jr2u5z) |
| [天高地厚](https://river.to/occasus/rawrsnh9zakqdx) | [rawrsnh9zakqdx](https://rsshub.app/the/category/rawrsnh9zakqdx) |
| [Oyster](https://river.to/occasus/rawdhl9hugdfn9) | [rawdhl9hugdfn9](https://rsshub.app/the/category/rawdhl9hugdfn9) |`,
categories: ["new-media"],
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportRadar: true,
supportBT: false,
supportPodcast: false,
supportScihub: false
},
radar: [
{
source: ["river.to/occasus/:category?"],
target: (params) => {
const category = params.category;
return `/the${category ? `/category/${category}` : ""}`;
}
},
{
title: "时局",
source: ["river.to/occasus/rawj7o4ypewv94"],
target: "/the/category/rawj7o4ypewv94"
},
{
title: "剩余价值",
source: ["river.to/occasus/rawmw7dsta2jew"],
target: "/the/category/rawmw7dsta2jew"
},
{
title: "Beijing",
source: ["river.to/occasus/rawbcvxkktdkq8"],
target: "/the/category/rawbcvxkktdkq8"
},
{
title: "稳中向好",
source: ["river.to/occasus/raw4krvx85dh27"],
target: "/the/category/raw4krvx85dh27"
},
{
title: "水深火热",
source: ["river.to/occasus/rawtn8jpsc6uvv"],
target: "/the/category/rawtn8jpsc6uvv"
},
{
title: "东升西降",
source: ["river.to/occasus/rawai5kd4z15il"],
target: "/the/category/rawai5kd4z15il"
},
{
title: "大局",
source: ["river.to/occasus/raw2efkzejrsx8"],
target: "/the/category/raw2efkzejrsx8"
},
{
title: "境外势力",
source: ["river.to/occasus/rawmpalhnlphuc"],
target: "/the/category/rawmpalhnlphuc"
},
{
title: "副刊",
source: ["river.to/occasus/rawxght2jr2u5z"],
target: "/the/category/rawxght2jr2u5z"
},
{
title: "天高地厚",
source: ["river.to/occasus/rawrsnh9zakqdx"],
target: "/the/category/rawrsnh9zakqdx"
},
{
title: "Oyster",
source: ["river.to/occasus/rawdhl9hugdfn9"],
target: "/the/category/rawdhl9hugdfn9"
}
]
};
//#endregion
export { handler, route };