Rsshub route
RSSHub route builder skill — generates complete, PR-ready RSSHub route code (namespace.ts + handler + Radar rules + PR checklist) from a target website
npx -y skills add wha7ev9r/rsshub-route-skill --skill rsshub-routeAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 22 days oldThe repository was created 22 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Create complete, PR-ready RSSHub route code — namespace.ts, route handler (API/HTML/Puppeteer), Radar rules, and PR checklist. Use whenever the user wants to build an RSS feed for a website via RSSHub, add/contribute a route to DIYgod/RSSHub, convert a site or API into an RSS source, or turn website updates into a subscribable feed. Trigger on: "RSSHub", "RSS route", "rsshub route", "RSS feed for X", "为网站制作 RSS", "给 X 做 RSS 订阅", "订阅 X 的更新", namespace.ts, radar rules, route handler, DataItem, or requests to monitor site updates as RSS — even without explicitly saying "RSSHub". Also trigger when the user is already writing RSSHub route code and needs help with cheerio selectors, ofetch, cache.tryGet, parseDate, Puppeteer, or the PR submission checklist. Do NOT use for: deploying/hosting RSSHub instances, debugging existing route runtime errors, tuning cache/config, general RSS reading, standalone non-RSSHub RSS projects, or non-RSSHub scraping.
SKILL.md
19.4 KB, as published. Nobody here has run it
RSSHub Route Builder
This skill produces complete, spec-compliant RSSHub route code that can be submitted as a Pull Request to DIYgod/RSSHub. It covers the full path: analyzing the target site → choosing a data-fetch strategy → writing namespace.ts + the route file + Radar rules → producing a PR-ready checklist.
RSSHub routes are TypeScript files that live under lib/routes/<namespace>/. Each route registers a Hono-style path and a handler that fetches data from the target site and returns it in a normalized shape; RSSHub's middleware turns that into RSS/Atom/JSON Feed.
How to use this skill
Follow the workflow below end-to-end. When you need the full type definitions (every field of Route, DataItem, Data, Namespace), read references/types-reference.md. When you need copy-paste-complete code templates for the three fetch strategies, read references/examples.md. When you need detailed signatures for the utility functions (ofetch, cache, parseDate, cheerio, puppeteer), read references/utils-reference.md.
Workflow
1. Understand the target
Ask the user (or infer from the URL) what content they want as a feed — e.g. "latest issues of a GitHub repo", "new posts in a forum section", "new videos from a channel". Identify:
- The target URL pattern (what page lists the items the user wants).
- The parameters the user should be able to customize (user id, board id, keyword, etc.) — these become
:paramsegments in the route path. - Whether the site exposes a public API, returns server-rendered HTML, or hides behind heavy anti-bot (this decides the fetch strategy).
If you can, open the target page (or its API docs / dev-tools network tab) and confirm where the list of items lives before writing code. Getting this right up front saves the most time.
2. Create the namespace
A namespace is a folder under lib/routes/ named after the site's second-level domain (SLD). For https://github.com/... the namespace is github; for https://www.zhihu.com/... it's zhihu. One site = one namespace — never create variants like zhihu-jp or zhihucom.
Create lib/routes/<namespace>/namespace.ts:
import type { Namespace } from "@/types";
export const namespace: Namespace = {
name: "Site Display Name", // human-readable, becomes the doc heading
url: "example.com", // site URL without protocol
description: "", // optional tips for users, supports markdown
lang: "en", // optional, main language of the site
};
If the namespace folder already exists (the site already has other routes), skip this step — just add your new route file alongside the existing ones.
3. Write the route file
Create lib/routes/<namespace>/<route-name>.ts. The file exports a single route object of type Route. Below is a fully annotated minimal example; see references/types-reference.md for every field.
import { Route } from "@/types";
import ofetch from "@/utils/ofetch";
import { parseDate } from "@/utils/parse-date";
export const route: Route = {
path: "/issue/:user/:repo/:state?", // Hono routing syntax; ? = optional
name: "Repo Issues", // human-readable, becomes the doc sub-heading
url: "github.com",
example: "/github/issue/DIYgod/RSSHub/open",
parameters: {
user: "GitHub username",
repo: "GitHub repo name",
state: {
description: "issue state",
default: "open",
options: [
{ label: "Open", value: "open" },
{ label: "Closed", value: "closed" },
],
},
},
categories: ["programming"],
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ["github.com/:user/:repo/issues", "github.com/:user/:repo"],
target: "/issue/:user/:repo",
},
],
maintainers: ["your-github-handle"],
handler,
};
async function handler(ctx) {
const { user, repo = "RSSHub" } = ctx.req.param();
// ... fetch + transform ...
return {
title: `${user}/${repo} issues`,
link: `https://github.com/${user}/${repo}/issues`,
item: [/* DataItem[] */],
};
}
A few things that trip people up:
- The handler must be
asyncbecause it awaits HTTP requests. Define it asasync function handler(ctx)(cleaner for non-trivial logic) or inline ashandler: async (ctx) => { ... }. - Route paths use Hono syntax.
:paramis required,:param?is optional, literal segments match exactly. The namespace is prepended automatically — sopath: '/issue/:user'inside thegithubnamespace becomes/github/issue/:user. exampleis the full path with the namespace and concrete values, e.g./github/issue/DIYgod/RSSHub. It must satisfy thepathpattern.maintainersis a list of GitHub usernames — put the user's handle here (ask them if not obvious).
4. Choose the fetch strategy
This is the most important decision. Pick the simplest strategy that works:
| Strategy | Use when | Key imports |
|---|---|---|
| API (preferred) | The site exposes a JSON/REST API, or you can spot XHR/fetch calls in dev-tools that return structured data | ofetch from @/utils/ofetch |
| HTML | No API; the items are rendered in server-side HTML you can select with CSS selectors | ofetch + load from cheerio |
| Puppeteer | The site blocks plain HTTP requests, renders content with JS, or uses heavy anti-bot | puppeteer from @/utils/puppeteer |
Always try API first — it's the most stable and easiest to parse. Only escalate to HTML when there's no API, and to Puppeteer when HTML fetching gets blocked.
For complete, copy-paste-ready templates of all three strategies (including full-text retrieval and caching), read references/examples.md.
5. Build the items
Whatever strategy you use, the goal is the same: produce an array of DataItem objects. The essential fields:
| Field | Type | Notes |
|---|---|---|
title | string | Item title (required) |
link | string | Absolute URL to the item page |
description | string | Item body / summary — HTML is fine |
pubDate | Date|string|number | Use parseDate() to normalize |
author | string | Author name |
category | string[] | Tags / categories |
If the list page doesn't include the full content, fetch each detail page to fill description. Wrap detail-page fetches in cache.tryGet() so repeated requests don't hammer the server — this is both courteous and required by the spec for full-text routes.
6. Write Radar rules
Radar rules let the RSSHub Radar browser extension auto-suggest your feed when the user visits a matching page. The source array lists URL patterns (no protocol) that should trigger the suggestion; target is the RSSHub route path with params filled from the source match.
radar: [
{
source: [
"github.com/:user/:repo/issues",
"github.com/:user/:repo/issues/:id",
],
target: "/issue/:user/:repo",
},
];
The :user and :repo in source capture values from the real URL; those same names in target produce the subscription URL. Keep source patterns specific enough to avoid false matches.
7. Verify and produce the PR checklist
Before declaring done, run through this checklist (it mirrors the official RSSHub PR template):
-
namespace.tsexists (or already did) with correct SLD name - Route file exports
route: Routewith all required fields (path,name,maintainers,example,handler) -
examplematches thepathpattern and includes concrete values + namespace -
categoriesis set to a valid category (seereferences/types-reference.mdfor the full list) -
featuresaccurately reflects reality — setrequirePuppeteer: trueif you used Puppeteer,antiCrawler: trueif the site has anti-bot,requireConfigif env vars are needed -
handlerisasyncand returns aDataobject withtitle,link, anditem: DataItem[] - Dates parsed via
parseDate()/parseRelativeDate()— never raw strings thatDate.parsemight misinterpret - Full-text routes use
cache.tryGet()for detail-page fetches -
radarrules present with sensiblesource/target - No
anytypes where a concrete type exists; no unused imports -
pnpm devstarts cleanly and the example URL returns valid RSS athttp://localhost:1200
When submitting the PR: title follows conventional commits — route(new): add <site> <what> for a new route. In the PR body's routes block, list the concrete example paths (one per line), e.g. /github/issue/DIYgod/RSSHub. Never write NOROUTE for a route PR.
8. Submit to the official RSSHub repository
Once the route passes local verification, the final step is opening a Pull Request to DIYgod/RSSHub so it gets merged and becomes available to all RSSHub users.
Fork and branch
- Fork the repo on GitHub (top-right "Fork" button on the RSSHub repo page).
- Clone your fork locally and add the upstream remote:
git clone https://github.com/<your-handle>/RSSHub.git cd RSSHub git remote add upstream https://github.com/DIYgod/RSSHub.git - Sync with upstream before starting (the repo moves fast):
git fetch upstream git checkout master git merge upstream/master - Create a feature branch — use a descriptive name tied to the site and route:
git checkout -b route/example-site-issue
Local verification
Before committing, make sure everything works:
pnpm install # install dependencies
pnpm dev # start dev server at http://localhost:1200
Open http://localhost:1200 + your example path (e.g. http://localhost:1200/example/updates/news) and confirm valid RSS is returned. Check the console for errors. If the route uses full-text fetching, verify detail pages load and cache works on a second request.
Also run the linter to catch style issues the CI will flag:
pnpm lint
Commit
Stage only the files you created/modified — typically lib/routes/<namespace>/namespace.ts, the route .ts file, and any radar.ts if needed. Don't commit assets/build/ or generated files.
git add lib/routes/<namespace>/
git commit -m "route(new): add <site> <route-name>"
The commit message scope matters — route(new) for a brand-new route, route for modifications to an existing one. This becomes the squash-merge commit message when the PR is merged.
Open the Pull Request
Push to your fork and open a PR targeting DIYgod/RSSHub:master. The PR body must follow the official template — fill in each section:
Involved Issue — if there's a related feature request or bug report, write Close #123. Leave blank if none. Multiple issues: Close #123, Close #456.
路由地址示例 (Example routes) — list the concrete example URLs (with real parameter values, not :param placeholders), one per line inside a ```routes code block:
```routes
/example/updates/news
/example/updates/blog
Do NOT write `/example/updates/:category` — use real values. If the PR is not route-related (e.g. docs only), write `NOROUTE` instead. For route PRs, never write `NOROUTE` or the PR will be auto-closed.
**新 RSS 路由检查表 (New RSS Route Checklist)** — check the boxes that apply by changing `[ ]` to `[x]`:
- [x] New Route / 新的路由
- [x] Follows Script Standard / 跟随路由规范
- [x] Documentation / 文档说明
- [x] Full text / 全文获取 (if the route fetches detail pages)
- [x] Use cache / 使用缓存 (if using `cache.tryGet`)
- [x] Anti-bot or rate limit / 反爬/频率限制 (if the site has anti-bot, note whether your code handles it)
- [x] Date and time / 日期和时间 (if the route provides `pubDate`)
Not every box needs checking, but the route must follow the [Script Standard](https://docs.rsshub.app/joinus/advanced/script-standard) — that's mandatory.
**说明 (Description)** — add any extra context: what the route does, quirks about the site's API/HTML, rate-limiting notes, etc.
#### Code review
RSSHub maintainers and automated bots will review the PR. You can check the status of automated checks by clicking "Details" next to each check name. Common feedback:
- **Linting errors** — fix and push to the same branch; the PR auto-updates.
- **Selector suggestions** — maintainers may propose more robust cheerio selectors. Use GitHub's "Add suggestion to batch" button to accept multiple suggestions at once, then commit.
- **Missing fields** — if a maintainer points out a missing `features` flag or `radar` rule, add it and push.
- **Cache usage** — if your route fetches detail pages but doesn't use `cache.tryGet`, a maintainer will likely ask you to add it.
Respond to all comments, push fixes to the same branch, and the PR updates automatically. Be patient — maintainers are volunteers.
#### After merge
Once merged, RSSHub's CI builds a new Docker image for multiple platforms (`linux/arm/v7`, `linux/arm64`, `linux/amd64`, with and without Chromium). This can take up to an hour. After the build completes, the route is live on all public RSSHub instances and available to every RSSHub user.
The route documentation is auto-generated from the `namespace.ts` and route file fields (`name`, `description`, `parameters`, `categories`, `example`) — no separate docs PR is needed. The generated docs appear on [docs.rsshub.app](https://docs.rsshub.app) under the matching category.
## Utility quick reference
These are the building blocks you'll reach for constantly. Full signatures and edge cases are in `references/utils-reference.md`.
**Fetch data — `ofetch`** (`@/utils/ofetch`): the standard request library. Auto-throws on non-2xx, parses JSON when the response is JSON, returns the body directly.
```ts
const data = await ofetch("https://api.example.com/list", {
headers: { Accept: "application/json" },
query: { page: 1 }, // ofetch supports `query` for query params
});
Parse HTML — cheerio (cheerio): jQuery-like selector API.
import { load } from "cheerio";
const $ = load(htmlString);
const items = $("ul.items > li")
.toArray()
.map((el) => {
const $el = $(el);
return {
title: $el.find("a").text(),
link: new URL($el.find("a").attr("href"), baseUrl).href,
};
});
Dates — parseDate (@/utils/parse-date): wraps day.js, returns a Date.
parseDate("2024-01-15"); // ISO
parseDate("2024/01/15", "YYYY/MM/DD"); // explicit format
parseRelativeDate("2天前"); // relative strings
Timezone — timezone (@/utils/timezone): shift a Date by an hourly offset.
timezone(parseDate("2024-01-15 13:00"), +8); // treat as UTC+8
Cache — cache (@/utils/cache): memoize detail-page fetches.
const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
const detail = await ofetch(item.link);
item.description = /* extract from detail */;
return item;
})
)
);
Puppeteer — puppeteer (@/utils/puppeteer): browser automation for stubborn sites.
const browser = await puppeteer();
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on("request", (r) =>
r.resourceType() === "document" ? r.continue() : r.abort(),
);
await page.goto(url, { waitUntil: "domcontentloaded" });
const html = await page.content();
page.close();
// ... parse with cheerio ...
browser.close(); // always close when done
Logger — logger (@/utils/logger): Puppeteer requests aren't auto-logged like ofetch, so log them manually: logger.http(\Requesting ${url}`)`.
Config — config (@/config): read env vars for tokens/keys. Check config.<namespace> at runtime rather than assuming a key exists.
Common pitfalls
- Relative links:
cheerio's.attr('href')returns relative URLs. Always absolutize withnew URL(href, baseUrl).href— RSS readers need absolute URLs. - Cache closure scope: any variable assigned outside
cache.tryGet's callback won't update on a cache hit. Do all mutations inside the callback and return the mutated object. - Forgetting
await:Promise.allover detail fetches must be awaited; the handler returns the resolved array, not the promises. - Selector fragility: prefer semantic selectors (
ul.issues > li,[data-testid="..."]) over brittle auto-generated class names. When a site uses hashed class names, match the stable prefix with[class^="prefix"]. - Anti-bot without Puppeteer: if
ofetchgets 403/blocked, first try adding a realisticUser-Agentheader; only escalate to Puppeteer if that fails. Setfeatures.antiCrawler: trueeither way. - Empty results: if a feed can legitimately be empty (no new items), set
allowEmpty: truein the returnedDataso the middleware doesn't error. - Don't reinvent: RSSHub already bundles
ofetch,cheerio,parseDate,timezone,cache,puppeteer,logger. Import them from@/utils/*— never install your own HTTP/parsing libraries.
Reference files
references/types-reference.md— Complete TypeScript definitions forRoute,RouteItem,DataItem,Data,Namespace,RadarItem,ViewType, and theCategoryunion. Consult this when you're unsure which fields are required vs optional, or what shape a value should take.references/examples.md— Full, annotated code templates for the three fetch strategies (API, HTML, Puppeteer), each in two variants: list-only and full-text-with-cache. Copy these as starting points and adapt.references/utils-reference.md— Detailed signatures and usage notes for every utility import (ofetch,cache,parseDate,parseRelativeDate,timezone,cheerio,puppeteer,logger,config).