agentsclimarketplace

Rsshub route

Skill wha7ev9r/rsshub-route-skill/rsshub-route

RSSHub route builder skill — generates complete, PR-ready RSSHub route code (namespace.ts + handler + Radar rules + PR checklist) from a target website

Install
npx -y skills add wha7ev9r/rsshub-route-skill --skill rsshub-route

Assembled 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 :param segments 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 async because it awaits HTTP requests. Define it as async function handler(ctx) (cleaner for non-trivial logic) or inline as handler: async (ctx) => { ... }.
  • Route paths use Hono syntax. :param is required, :param? is optional, literal segments match exactly. The namespace is prepended automatically — so path: '/issue/:user' inside the github namespace becomes /github/issue/:user.
  • example is the full path with the namespace and concrete values, e.g. /github/issue/DIYgod/RSSHub. It must satisfy the path pattern.
  • maintainers is 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:

StrategyUse whenKey imports
API (preferred)The site exposes a JSON/REST API, or you can spot XHR/fetch calls in dev-tools that return structured dataofetch from @/utils/ofetch
HTMLNo API; the items are rendered in server-side HTML you can select with CSS selectorsofetch + load from cheerio
PuppeteerThe site blocks plain HTTP requests, renders content with JS, or uses heavy anti-botpuppeteer 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:

FieldTypeNotes
titlestringItem title (required)
linkstringAbsolute URL to the item page
descriptionstringItem body / summary — HTML is fine
pubDateDate|string|numberUse parseDate() to normalize
authorstringAuthor name
categorystring[]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.ts exists (or already did) with correct SLD name
  • Route file exports route: Route with all required fields (path, name, maintainers, example, handler)
  • example matches the path pattern and includes concrete values + namespace
  • categories is set to a valid category (see references/types-reference.md for the full list)
  • features accurately reflects reality — set requirePuppeteer: true if you used Puppeteer, antiCrawler: true if the site has anti-bot, requireConfig if env vars are needed
  • handler is async and returns a Data object with title, link, and item: DataItem[]
  • Dates parsed via parseDate() / parseRelativeDate() — never raw strings that Date.parse might misinterpret
  • Full-text routes use cache.tryGet() for detail-page fetches
  • radar rules present with sensible source/target
  • No any types where a concrete type exists; no unused imports
  • pnpm dev starts cleanly and the example URL returns valid RSS at http://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

  1. Fork the repo on GitHub (top-right "Fork" button on the RSSHub repo page).
  2. 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
    
  3. Sync with upstream before starting (the repo moves fast):
    git fetch upstream
    git checkout master
    git merge upstream/master
    
  4. 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 with new 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.all over 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 ofetch gets 403/blocked, first try adding a realistic User-Agent header; only escalate to Puppeteer if that fails. Set features.antiCrawler: true either way.
  • Empty results: if a feed can legitimately be empty (no new items), set allowEmpty: true in the returned Data so 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 for Route, RouteItem, DataItem, Data, Namespace, RadarItem, ViewType, and the Category union. 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).

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.