From a031761d78dfed3c3ee0338a060598767135c448 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Mar 2023 10:58:37 +0000 Subject: [PATCH 1/8] chore(deps): bump proxy-chain from 2.2.1 to 2.3.0 (#12194) Bumps [proxy-chain](https://github.com/apify/proxy-chain) from 2.2.1 to 2.3.0. - [Release notes](https://github.com/apify/proxy-chain/releases) - [Changelog](https://github.com/apify/proxy-chain/blob/master/CHANGELOG.md) - [Commits](https://github.com/apify/proxy-chain/compare/v2.2.1...v2.3.0) --- updated-dependencies: - dependency-name: proxy-chain dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 939436eb1cfa1b..5cce293fa4a9d4 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "module-alias": "2.2.2", "pidusage": "3.0.2", "plist": "3.0.6", - "proxy-chain": "2.2.1", + "proxy-chain": "2.3.0", "puppeteer": "19.8.0", "puppeteer-extra": "3.3.6", "puppeteer-extra-plugin-stealth": "2.11.2", diff --git a/yarn.lock b/yarn.lock index 86c691e58d7b19..c33f97cecbbbd5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11232,10 +11232,10 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -proxy-chain@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/proxy-chain/-/proxy-chain-2.2.1.tgz#da598d81bf4f48f8c9808700aa7615539985b32c" - integrity sha512-slwZPK9KQW1TEefAfMzhZ+bBbmQA+nT+zg/eA9iWNv6qqq8XxC912B05zPE1uhq5sAdZnEPvfy3z7qIDMwHlMQ== +proxy-chain@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/proxy-chain/-/proxy-chain-2.3.0.tgz#5a20aa018cc825703159954e43828821932f7b23" + integrity sha512-S1m0Ao0IGoXfy14dKXAaJCUkCBCnleYzElzoATj42Iq4d8EYqOZq4jS4G/D94AzrU6QaPOzzoEDjbm7VJQGq9g== dependencies: tslib "^2.3.1" From 8614ec3b7e99b533e9e77efa2ba3eef7543b7052 Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 29 Mar 2023 14:31:58 +0000 Subject: [PATCH 2/8] docs: quick start revamp (#12198) * docs: revamp quick start * wip * wip * wip * wip * wip * wip * wip: radar * wip: rich media * wip: script standard * wip: cache * wip: chinese quick start * wip: chinese quick start part 2 * wip: chinese quick start * docs: add open collective * retrigger ci --- .devcontainer/devcontainer.json | 1 + .github/FUNDING.yml | 1 + .github/PULL_REQUEST_TEMPLATE.md | 12 +- .gitpod.yml | 2 +- .markdownlint.jsonc | 11 + README.md | 3 +- docs/.vuepress/config.js | 130 ++++ docs/README.md | 2 +- docs/en/README.md | 2 +- docs/en/api.md | 4 +- docs/en/blog.md | 1 - docs/en/game.md | 8 +- docs/en/install/README.md | 2 +- docs/en/joinus/advanced-feed.md | 198 ++++++ docs/en/joinus/debug.md | 26 + docs/en/joinus/new-radar.md | 286 +++++++++ docs/en/joinus/new-rss/add-docs.md | 182 ++++++ docs/en/joinus/new-rss/before-start.md | 153 +++++ docs/en/joinus/new-rss/prerequisites.md | 39 ++ docs/en/joinus/new-rss/start-code.md | 769 ++++++++++++++++++++++++ docs/en/joinus/new-rss/submit-route.md | 122 ++++ docs/en/joinus/pub-date.md | 49 +- docs/en/joinus/quick-start.md | 611 +------------------ docs/en/joinus/script-standard.md | 196 +++--- docs/en/joinus/use-cache.md | 85 ++- docs/en/journal.md | 18 +- docs/en/multimedia.md | 4 +- docs/en/new-media.md | 16 +- docs/en/programming.md | 7 +- docs/en/support/README.md | 5 +- docs/en/usage.md | 4 +- docs/joinus/advanced-feed.md | 198 ++++++ docs/joinus/debug.md | 26 + docs/joinus/new-radar.md | 282 +++++++++ docs/joinus/new-rss/add-docs.md | 181 ++++++ docs/joinus/new-rss/before-start.md | 153 +++++ docs/joinus/new-rss/prerequisites.md | 39 ++ docs/joinus/new-rss/start-code.md | 769 ++++++++++++++++++++++++ docs/joinus/new-rss/submit-route.md | 122 ++++ docs/joinus/pub-date.md | 51 +- docs/joinus/quick-start.md | 607 +------------------ docs/joinus/script-standard.md | 199 +++--- docs/joinus/use-cache.md | 83 ++- docs/support/README.md | 1 + 44 files changed, 4199 insertions(+), 1461 deletions(-) create mode 100644 .markdownlint.jsonc create mode 100644 docs/en/joinus/advanced-feed.md create mode 100644 docs/en/joinus/debug.md create mode 100644 docs/en/joinus/new-radar.md create mode 100644 docs/en/joinus/new-rss/add-docs.md create mode 100644 docs/en/joinus/new-rss/before-start.md create mode 100644 docs/en/joinus/new-rss/prerequisites.md create mode 100644 docs/en/joinus/new-rss/start-code.md create mode 100644 docs/en/joinus/new-rss/submit-route.md create mode 100644 docs/joinus/advanced-feed.md create mode 100644 docs/joinus/debug.md create mode 100644 docs/joinus/new-radar.md create mode 100644 docs/joinus/new-rss/add-docs.md create mode 100644 docs/joinus/new-rss/before-start.md create mode 100644 docs/joinus/new-rss/prerequisites.md create mode 100644 docs/joinus/new-rss/start-code.md create mode 100644 docs/joinus/new-rss/submit-route.md diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a2b30e99d4ffcb..11453f54acf367 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,6 +17,7 @@ "esbenp.prettier-vscode", "deepscan.vscode-deepscan", "rangav.vscode-thunder-client", + "SonarSource.sonarlint-vscode", "ZihanLi.at-helper" ] } diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index bebc156ede8682..77ae687b4d528c 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,5 @@ # These are supported funding model platforms github: DIYgod +open_collective: RSSHub patreon: DIYgod custom: ['https://afdian.net/@diygod', 'https://diygod.me/images/zfb.jpg', 'https://diygod.me/images/wx.jpg'] diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7f799138fe9259..b1102ddfa81886 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,13 +1,13 @@ -## 该 PR 相关 Issue / Involved issue +## 该 PR 相关 Issue / Involved Issue Close # -## 路由地址示例 / Example for the proposed route(s) +## 路由地址示例 / Example for the Proposed Route(s) ```routes ``` -## 新 RSS 检查列表 / New RSS Script Checklist +## 新 RSS 路由检查表 / New RSS Route Checklist - [ ] 新的路由 New Route - [ ] 跟随 [v2 路由规范](https://docs.rsshub.app/joinus/script-standard.html) Follows [v2 Script Standard](https://docs.rsshub.app/en/joinus/script-standard.html) diff --git a/.gitpod.yml b/.gitpod.yml index c33d24d2e9535f..af467b2cbeac93 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -31,7 +31,7 @@ vscode: - esbenp.prettier-vscode - deepscan.vscode-deepscan - rangav.vscode-thunder-client - - TabNine.tabnine-vscode + - sonarsource.sonarlint-vscode # - ZihanLi.at-helper not available on Open VSX github: diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc new file mode 100644 index 00000000000000..e426ea78adf606 --- /dev/null +++ b/.markdownlint.jsonc @@ -0,0 +1,11 @@ +{ + "MD007": { "indent": 4 }, // ul-indent - Unordered list indentation + "MD013": false, // line-length - Line length + "MD014": false, // commands-show-output - Dollar signs used before commands without showing output + "MD024": { "siblings_only": true }, // no-duplicate-heading/no-duplicate-header - Multiple headings with the same content + "MD030": { "ul_single": 3, "ol_single": 2, "ul_multi": 3, "ol_multi": 2 }, // list-marker-space - Spaces after list markers + "MD033": false, // no-inline-html - Inline HTML + "MD036": false, // no-emphasis-as-heading/no-emphasis-as-header - Emphasis used instead of a heading + "MD040": false, // fenced-code-language - Fenced code blocks should have a language specified + "MD051": false // link-fragments - Link fragments should be valid +} diff --git a/README.md b/README.md index 8a56612f7f648e..72d5cc9d19fb90 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

-RSSHub +RSSHub

RSSHub

@@ -87,6 +87,7 @@ You can support RSSHub via donations. Recurring donors will be rewarded via express issue response, or even have your name displayed on our GitHub page and website. - Become a Sponser on [GitHub](https://github.com/sponsors/DIYgod) +- Become a Sponser on [Open Collective](https://opencollective.com/RSSHub) - Become a Sponser on [Patreon](https://www.patreon.com/DIYgod) - Become a Sponser on [爱发电](https://afdian.net/@diygod) - Contact us directly: i@diygod.me diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index ba88807e2de804..7e09cc25fb2e1a 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -46,6 +46,7 @@ module.exports = { anchor: { level: 999, // Disable original Plugin }, + lineNumbers: true, extendMarkdown: (md) => { md.use(require('../.format/md/hierarchySlug'), { slugify(s) { @@ -83,6 +84,7 @@ module.exports = { editLinks: true, docsDir: 'docs', smoothScroll: true, + logo: '/logo.png', locales: { '/': { lang: 'zh-CN', @@ -92,6 +94,70 @@ module.exports = { lastUpdated: '上次更新', nav: require('./nav/zh'), sidebar: { + '/joinus/': [ + { + title: '👥 参与我们', + path: '/joinus/quick-start.html', + }, + { + title: '📰 提交新的 RSSHub 规则', + path: '/joinus/new-rss/prerequisites.html', + collapsable: false, + children: [ + { + title: '🔑 准备工作', + path: 'new-rss/prerequisites', + }, + { + title: '💡 开始之前', + path: 'new-rss/before-start', + }, + { + title: '🚀 制作自己的 RSSHub 路由', + path: 'new-rss/start-code', + }, + { + title: '📖 添加文档', + path: 'new-rss/add-docs', + }, + { + title: '📤 提交路由', + path: 'new-rss/submit-route', + }, + ], + }, + { + title: '📡 提交新的 RSSHub Radar 规则', + path: '/joinus/new-radar.html', + }, + { + title: '💪 Advanced', + path: '/joinus/advanced-feed.html', + collapsable: false, + children: [ + { + title: '🎧 制作多媒体 RSS 订阅源', + path: 'advanced-feed', + }, + { + title: '📜 路由规范', + path: 'script-standard', + }, + { + title: '💾 使用缓存', + path: 'use-cache', + }, + { + title: '🗓️ 日期处理', + path: 'pub-date', + }, + { + title: '🐛 调试', + path: 'debug', + }, + ], + }, + ], '/': [ { title: '指南', @@ -139,6 +205,70 @@ module.exports = { lastUpdated: 'Last Updated', nav: require('./nav/en'), sidebar: { + '/en/joinus/': [ + { + title: '👥 Join Us', + path: '/en/joinus/quick-start.html', + }, + { + title: '📰 New RSSHub rules', + path: '/en/joinus/new-rss/prerequisites.html', + collapsable: false, + children: [ + { + title: '🔑 Prerequisites', + path: 'new-rss/prerequisites', + }, + { + title: '💡 Just before you start', + path: 'new-rss/before-start', + }, + { + title: '🚀 Create your own RSSHub route', + path: 'new-rss/start-code', + }, + { + title: '📖 Add documentation', + path: 'new-rss/add-docs', + }, + { + title: '📤 Submit your route', + path: 'new-rss/submit-route', + }, + ], + }, + { + title: '📡 New Radar Rules', + path: '/en/joinus/new-radar.html', + }, + { + title: '💪 Advanced', + path: '/en/joinus/advanced-feed.html', + collapsable: false, + children: [ + { + title: '🎧 Create a Rich Media RSS Feed', + path: 'advanced-feed', + }, + { + title: '📜 Script Standard', + path: 'script-standard', + }, + { + title: '💾 Caching', + path: 'use-cache', + }, + { + title: '🗓️ Date Handling', + path: 'pub-date', + }, + { + title: '🐛 Debugging', + path: 'debug', + }, + ], + }, + ], '/en/': [ { title: 'Guide', diff --git a/docs/README.md b/docs/README.md index 5b8f839519be21..e3f7babc5869fb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -30,7 +30,7 @@ RSSHub 是一个开源、简单易用、易于扩展的 RSS 生成器,可以 ### Special Sponsors
-RSS3 +RSS3
[![](https://opencollective.com/static/images/become_sponsor.svg)](/support/) diff --git a/docs/en/README.md b/docs/en/README.md index 8ff4f7383ed438..6259a74510c4f1 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -30,7 +30,7 @@ RSSHub can be used with browser extension [RSSHub Radar](https://github.com/DIYg ### Special Sponsors
-RSS3 +RSS3
[![](https://opencollective.com/static/images/become_sponsor.svg)](/en/support/) diff --git a/docs/en/api.md b/docs/en/api.md index ec91e1fe0f637d..adbe096534f1d0 100644 --- a/docs/en/api.md +++ b/docs/en/api.md @@ -20,7 +20,7 @@ Parameters: - `name`, route's top level name as in [https://github.com/DIYgod/RSSHub/tree/master/lib/routes](https://github.com/DIYgod/RSSHub/tree/master/lib/routes). Optional, **returns all public routes if not specified**. -A successful request returns a HTTP status code `200 OK` with the result in JSON: +A successful request returns an HTTP status code `200 OK` with the result in JSON: ```js { @@ -39,4 +39,4 @@ A successful request returns a HTTP status code `200 OK` with the result in JSON } ``` -If no matching results were found, the server returns only a HTTP status code `204 No Content`. +If no matching results were found, the server returns only an HTTP status code `204 No Content`. diff --git a/docs/en/blog.md b/docs/en/blog.md index 2c6da143b36bb0..184bf3d57abf26 100644 --- a/docs/en/blog.md +++ b/docs/en/blog.md @@ -119,4 +119,3 @@ Limit the number of entries to be retrieved by adding `?limit=x` to the end of t ### Entry - diff --git a/docs/en/game.md b/docs/en/game.md index ff0137c3f12805..d662d77f0446de 100755 --- a/docs/en/game.md +++ b/docs/en/game.md @@ -269,9 +269,9 @@ For instance, in `https://store.steampowered.com/search/?specials=1&term=atelier Steam provides some official RSS feeds: -- News home page: [https://store.steampowered.com/feeds/news/?l=english](https://store.steampowered.com/feeds/news/?l=english) the parameter `l=english` specifiy the language. -- Game news rss can get from the rss buttom in page like this: [https://store.steampowered.com/news/app/648800/](https://store.steampowered.com/news/app/648800/), rss link will looks like: [https://store.steampowered.com/feeds/news/app/648800/?cc=US&l=english](https://store.steampowered.com/feeds/news/app/648800/?cc=US&l=english) -- Steam group can add `/rss` behind Steam community URL to subscribe: [https://steamcommunity.com/groups/SteamLabs/rss](https://steamcommunity.com/groups/SteamLabs/rss) or add the `/feeds` in Steam News : [https://store.steampowered.com/feeds/news/group/35143931/](https://store.steampowered.com/feeds/news/group/35143931/) +- News home page: [https://store.steampowered.com/feeds/news/?l=english](https://store.steampowered.com/feeds/news/?l=english) the parameter `l=english` specifiy the language. +- Game news rss can get from the rss buttom in page like this: [https://store.steampowered.com/news/app/648800/](https://store.steampowered.com/news/app/648800/), rss link will looks like: [https://store.steampowered.com/feeds/news/app/648800/?cc=US&l=english](https://store.steampowered.com/feeds/news/app/648800/?cc=US&l=english) +- Steam group can add `/rss` behind Steam community URL to subscribe: [https://steamcommunity.com/groups/SteamLabs/rss](https://steamcommunity.com/groups/SteamLabs/rss) or add the `/feeds` in Steam News : [https://store.steampowered.com/feeds/news/group/35143931/](https://store.steampowered.com/feeds/news/group/35143931/) ::: @@ -333,7 +333,7 @@ Unlike TapTap China Mainland Website, the International Website has no BBS. -News data from https://warthunder.com/en/news/ +News data from The year, month and day provided under UTC time zone are the same as the official website, so please ignore the specific time!!! diff --git a/docs/en/install/README.md b/docs/en/install/README.md index 846887bbce5d29..b78c8b39a142ff 100644 --- a/docs/en/install/README.md +++ b/docs/en/install/README.md @@ -102,7 +102,7 @@ Execute the following command to pull RSSHub's docker image. $ docker pull diygod/rsshub ``` -Start a RSSHub container +Start an RSSHub container ```bash $ docker run -d --name rsshub -p 1200:1200 diygod/rsshub diff --git a/docs/en/joinus/advanced-feed.md b/docs/en/joinus/advanced-feed.md new file mode 100644 index 00000000000000..015cd1a8bf8ffd --- /dev/null +++ b/docs/en/joinus/advanced-feed.md @@ -0,0 +1,198 @@ +# Create a Rich Media RSS Feed + +This guide is intended for advanced users who want to know how to create an RSS feed in detail. If you're new to creating RSS feeds, we recommend reading [Create Your Own RSSHub Route](/en/joinus/new-rss/start-code.html) first. + +Once you have collected the data you want to include in your RSS feed, you can pass it to `ctx.state.data`. RSSHub's middleware [`template.js`](https://github.com/DIYgod/RSSHub/blob/master/lib/middleware/template.js) will then process the data and render the RSS output in the required format (which is RSS 2.0 by default). In addition to the fields mentioned in [Create your own RSSHub route](/en/joinus/new-rss/start-code.html), you can customize your RSS feed further using the following fields. + +It's important to note that not all fields are applicable to all output formats since RSSHub supports multiple output formats. The table below shows which fields are compatible with different output formats. We use the following symbols to denote compatibility: `A` for Atom, `J` for JSON Feed, `R` for RSS 2.0. + +Channel level + +The following table lists the fields you can use to customize your RSS feed at channel level: + +| Field | Description | Default | Compatibility | +| :---------- | :---------- | :----------- | :------------ | +| **`title`** | *(Recommended)* The name of the feed, which should be plain text only | `RSSHub` | A, J, R | +| **`link`** | *(Recommended)* The URL of the website associated with the feed, which should link to a human-readable website | `https://rsshub.app` | A, J, R | +| **`description`** | *(Optional)* The summary of the feed, which should be plain text only | If not specified, defaults to **`title`** | J, R | +| **`language`** | *(Optional)* The primary language of the feed, which should be a value from [RSS Language Codes](https://www.rssboard.org/rss-language-codes) or ISO 639 language codes | `zh-cn` | J, R | +| **`image`** | *(Recommended)* The URL of the image that represents the channel, which should be relatively large and square | `undefinded` | J, R | +| **`icon`** | *(Optional)* The icon of an Atom feed | `undefinded` | J | +| **`logo`** | *(Optional)* The logo of an RSS feed | `undefinded` | J | +| **`subtitle`** | *(Optional)* The subtitle of an Atom feed | `undefinded` | A | +| **`author`** | *(Optional)* The author of an Atom feed or the authors of a JSON feed | `RSSHub` | A, J | +| **`itunes_author`** | *(Optional)* The author of a podcast feed | `undefinded` | R | +| **`itunes_category`** | *(Optional)* The category of a podcast feed | `undefinded` | R | +| **`itunes_explicit`** | *(Optional)* The explicit of a podcast feed | `undefinded` | R | +| **`allowEmpty`** | *(Optional)* Whether to allow empty feeds. If set to `true`, the feed will be generated even if there are no items | `undefinded` | A, J, R | + +Each item in an RSS feed is represented by an object with a set of fields that describe it. The table below lists the available fields: + +| Field | Description | Default | Compatibility | +| :---------- | :---------- | :------------- | :------------ | +| **`title`** | *(Required)* The title of the item, which should be plain text only | `undefinded` | A, J, R | +| **`link`** | *(Recommended)* The URL of the item, which should link to a human-readable website | `undefinded` | A, J, R | +| **`description`** | *(Recommended)* The content of the item. For an Atom feed, it's the `atom:content` element. For a JSON feed, it's the `content_html` field | `undefinded` | A, J, R | +| **`author`** | *(Optional)* The author of the item | `undefinded` | A, J, R | +| **`category`** | *(Optional)* The category of the item. You can use a plain string or an array of strings | `undefinded` | A, J, R | +| **`guid`** | *(Optional)* The unique identifier of the item | If not specified, defaults to **`link || title`** | A, J, R | +| **`pubDate`** | *(Recommended)* The publication date of the item, which should be a [Date object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date) following [the standard](/en/joinus/pub-date.html) | `undefinded` | A, J, R | +| **`updated`** | *(Optional)* The date of the last modification of the item, which should be a [Date object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date) | `undefinded` | A, J | +| **`itunes_item_image`** | *(Optional)* The URL of an image associated with the item | `undefinded` | R | +| **`itunes_duration`** | *(Optional)* The length of an audio or video item in seconds (or in the format H:mm:ss), which should be a number or string | `undefinded` | J, R | +| **`enclosure_url`** | *(Optional)* The URL of an enclosure associated with the item | `undefinded` | J, R | +| **`enclosure_length`** | *(Optional)* The size of the enclosure file in **byte**, which should be a number | `undefinded` | J, R | +| **`enclosure_type`** | *(Optional)* The MIME type of the enclosure file, which should be a string | `undefinded` | J, R | +| **`upvotes`** | *(Optional)* The number of upvotes the item has received, which should be a number | `undefinded` | A | +| **`downvotes`** | *(Optional)* The number of downvotes the item has received, which should be a number | `undefinded` | A | +| **`comments`** | *(Optional)* The number of comments for the item, which should be a number | `undefinded` | A | +| **`media.*`** | *(Optional)* The media associated with the item. See [Media RSS](https://www.rssboard.org/media-rss) for more details | `undefinded` | R | +| **`doi`** | *(Optional)* The Digital Object Identifier of the item, which should be a string in the format `10.xxxx/xxxxx.xxxx` | `undefinded` | R | + +::: warning Formatting Considerations +When specifying certain fields in an RSS feed, it's important to keep in mind some formatting considerations. Specifically, you should avoid including any linebreaks, consecutive whitespace, or leading/trailing whitespace in the following fields: **`title`**, **`subtitle`** (only for Atom), **`author`** (only for Atom), **`item.title`**, and **`item.author`**. + +While most RSS readers will automatically trim these fields, some may not process them properly. Therefore, to ensure compatibility with all RSS readers, we recommend trimming these fields before outputting them. If your route cannot tolerate trimming these fields, you should consider changing their format. + +Additionally, while other fields will not be forced to be trimmed, we suggest avoiding violations of the above formatting rules as much as possible. If you are using Cheerio to extract content from web pages, be aware that Cheerio will retain line breaks and indentation. For the **`item.description`** field, in particular, any intended linebreaks should be converted to `
` tags to prevent them from being trimmed by the RSS reader. If you're extracting an RSS feed from JSON data, be aware that the JSON may contain linebreaks that need to be displayed, so you should convert them to `
` tags in this case. + +It's important to keep these formatting considerations in mind to ensure your RSS feed is compatible with all RSS readers. +::: + +## Create a BitTorrent/Magnet Feed + +RSSHub allows you to create BitTorrent/Magnet feeds, which can be useful for triggering automated downloads. To create a BitTorrent/Magnet feed, you'll need to add **additional** fields to your RSS feed that are in accordance with many downloaders' subscription formats. + +Here's an example of how to create a BitTorrent/Magnet feed: + +```js +ctx.state.data = { + item: [ + { + enclosure_url: '', // This should be the Magnet URI + enclosure_length: '', // The file size in bytes (this field is optional) + enclosure_type: 'application/x-bittorrent', // This field should be fixed to 'application/x-bittorrent' + }, + ], +}; +``` + +By including these fields in your RSS feed, you'll be able to create BitTorrent/Magnet feeds that can be automatically downloaded by compatible downloaders. + +### Update the documentation + +If you're adding support for BitTorrent/Magnet feeds in your RSSHub route, it's important to update the documentation to reflect this change. To do this, you'll need to set the `supportBT` attribute of the `RouteEn` component to `"1"`. Here's an example: + +```vue + +``` + +By setting the `supportBT` attribute to `"1"`, you'll be able to update your documentation to accurately reflect your route's support for BitTorrent/Magnet feeds. + +## Create a Journal Feed + +RSSHub supports creating journal feeds that can replace `item.link` with a Sci-hub link if users provide the [common parameter](/en/parameter.html#sci-hub-link) `scihub`. To create a journal feed, you'll need to include an **additional** field in your RSS feed: + +```js +ctx.state.data = { + item: [ + { + doi: '', // This should be the DOI of the item (e.g., '10.47366/sabia.v5n1a3') + }, + ], +}; +``` + +By including this `doi` field in your RSS feed, you'll be able to create journal feeds that are compatible with RSSHub's Sci-hub functionality. + +### Update the documentation + +To update the documentation for your route with support for journal feeds, you'll need to set the `supportScihub` attribute of the RouteEn component to `"1"`. Here's an example: + +```vue + +``` + +By setting the `supportSciHub` attribute to `"1"`, the documentation for your route will accurately reflect its support for creating journal feeds with Sci-hub links. + +## Create a Podcast Feed + +RSSHub supports creating podcast feeds that can be used with many podcast players' subscription formats. To create a podcast feed, you'll need to include several **additional** fields in your RSS feed: + +```js +ctx.state.data = { + itunes_author: '', // This field is **required** and should specify the podcast author's name + itunes_category: '', // This field specifies the channel category + image: '', // This field specifies the channel's cover image or album art + item: [ + { + itunes_item_image: '', // This field specifies the item's cover image + itunes_duration: '', // This field is optional and specifies the length of the audio in seconds or the format H:mm:ss + enclosure_url: '', // This should be the item's direct audio link + enclosure_length: '', // This field is optional and specifies the size of the file in **bytes** + enclosure_type: '', // This field specifies the MIME type of the audio file (common types are 'audio/mpeg' for .mp3, 'audio/x-m4a' for .m4a, and 'video/mp4' for .mp4) + }, + ], +}; +``` + +By including these fields in your RSS feed, you'll be able to create podcast feeds that are compatible with many podcast players. + +### Update the documentation + +To update the documentation for your route with support for podcast feeds, you'll need to set the `supportPodcast` attribute of the `RouteEn` component to `"1"`. Here's an example: + +```vue + +``` + +By setting the `supportPodcast` attribute to `"1"`, the documentation for your route will accurately reflect its support for creating podcast feeds. + +## Create a Media Feed + +RSSHub supports creating [Media RSS](https://www.rssboard.org/media-rss) feeds that are compatible with many [Media RSS](https://www.rssboard.org/media-rss) software subscription formats. To create a [Media RSS](https://www.rssboard.org/media-rss) feed, you'll need to include those **additional** fields in your RSS feed. + +Here's an example of how to create a [Media RSS](https://www.rssboard.org/media-rss) feed: + +```js +ctx.state.data = { + item: [ + { + media: { + content: { + url: '...', // This should be the URL of the media content + type: '...', // This should be the MIME type of the media content (e.g., 'audio/mpeg' for an .mp3 file) + }, + thumbnail: { + url: '...', // This should be the URL of the thumbnail image + }, + '...': { + '...': '...', // Additional media properties can be included here + } + }, + }, + ], +}; +``` + +By including these fields in your RSS feed, you'll be able to create [Media RSS](https://www.rssboard.org/media-rss) feeds that are compatible with many [Media RSS](https://www.rssboard.org/media-rss) software subscription formats. + +## Create an Atom Feed with Interactions + +RSSHub supports creating Atom feeds that include interactions like upvotes, downvotes, and comments. To create an Atom feed with interactions, you'll need to include **additional** fields in your RSS feed that specify the interaction counts for each item. + +Here's an example of how to create an Atom feed with interactions: + +```js +ctx.state.data = { + item: [ + { + upvotes: 0, // This should be the number of upvotes for this item + downvotes: 0, // This should be the number of downvotes for this item + comments: 0, // This should be the number of comments for this item + }, + ], +}; +``` + +By including these fields in your Atom feed, you'll be able to create Atom feeds with interactions that are compatible with many Atom feed readers. diff --git a/docs/en/joinus/debug.md b/docs/en/joinus/debug.md new file mode 100644 index 00000000000000..5aea0c6dc076a7 --- /dev/null +++ b/docs/en/joinus/debug.md @@ -0,0 +1,26 @@ +--- +sidebarDepth: 0 +--- +# Debugging + +When debugging your code, you can use more than just `console.log` or attaching the node process to a debugger. Another option is to use `ctx.state.json` to provide a custom object for debugging. + +## Using `ctx.state.json` + +To pass a custom object to ctx.state.json for debugging, follow these steps: + +1. Create your custom object. +2. Assign your object to `ctx.state.json`. +3. Access the corresponding route + `.debug.json` to view your object. For example, if you want to debug the route `/furstar/characters/:lang?`, you can access the URL: `/furstar/characters/en.debug.json` + +Here's an example of how to use `ctx.state.json` taken from [furstar/index.js](https://github.com/DIYgod/RSSHub/blob/master/lib/v2/furstar/index.js) + +```js +const info = utils.fetchAllCharacters(res.data, base); + +ctx.state.json = { + info, +}; +``` + +In the example above, we're passing the `info` object to `ctx.state.json`, which we can then access using the corresponding route + `.debug.json`. diff --git a/docs/en/joinus/new-radar.md b/docs/en/joinus/new-radar.md new file mode 100644 index 00000000000000..260c924979c9ed --- /dev/null +++ b/docs/en/joinus/new-radar.md @@ -0,0 +1,286 @@ +# New RSSHub Radar Rules + +If you want to see the results, we suggest you install the browser extension. You can download it for your browser on the [Join Us](/en/joinus/quick-start.html#submit-new-rsshub-radar-rules-before-you-start) page. + +## Code the rule + +To create a new RSS feed, create a file called `radar.js` under the corresponding namespace in [/lib/v2/](https://github.com/DIYgod/RSSHub/tree/master/lib/v2). We will continue to use the example of creating an RSS feed for `GitHub Repo Issues`, which is described [here](/en/joinus/new-rss/before-start.html). The resulting code will look like this: + +```js +module.exports = { + 'github.com': { + _name: 'GitHub', + '.': [ + { + title: 'Repo Issues', + docs: 'https://docs.rsshub.app/en/programming.html#github', + source: ['/:user/:repo/issues/:id', '/:user/:repo/issues', '/:user/:repo'], + target: '/github/issue/:user/:repo', + }, + ], + }, +}; +``` + +## Top-level Object key + +The object key is the domain name without any subdomains, URL path, or protocol. + +![Struction of a URL](https://wsrv.nl/?url=https://enwpgo.files.wordpress.com/2022/10/image-30.png&output=webp) + +In this case, the domain name is `github.com`, so the object key is `github.com`. + +## Inner object key + +The first inner object key is `_name`, which is the name of the website. This should be the same as the level 2 heading (`##`) of the route documentation. In this case, it's `GitHub`. + +The rest of the inner object keys are the subdomains of a website. If a website you want to match does not have any subdomains, or you want to match both `www.example.com` and `example.com`, use `'.'` instead. In this case, we will use `'.'` since we want to match `github.com`. Note that each subdomain should return **an array of objects**. + + + + +```js{4} +module.exports = { + 'github.com': { + _name: 'GitHub', + '.': [ + { + title: '...', + docs: '...', + source: ['/...'], + target: '/...', + }, + ], + }, +}; +``` + + + + +```js{4} +module.exports = { + 'github.com': { + _name: 'GitHub', + abc: [ + { + title: '...', + docs: '...', + source: ['/...'], + target: '/...', + }, + ], + }, +}; +``` + + + + +```js{4} +module.exports = { + 'github.com': { + _name: 'GitHub', + 'abc.def': [ + { + title: '...', + docs: '...', + source: ['/...'], + target: '/...', + }, + ], + }, +}; +``` + + + + +### `title` + +The title is a *required* field and should be the same as the level 3 heading (`###`) of the route documentation. In this case, it's `Repo Issues`. Do not repeat the website name (`_name`), which is `GitHub`, in `title`. + +### `docs` + +The documentation link is also a *required* field. In this case, the documentation link for `GitHub Repo Issues` will be `https://docs.rsshub.app/en/programming.html#github`. + +Note that the hash should be positioned to the level 2 heading (`##`), and not `https://docs.rsshub.app/en/programming.html#github-repo-issues`. + +### `source` + +The source field is *optional* and should specifies the URL path. Leave it blank if you don't want to match any URL paths. It only appears in `RSSHub for the current site` option of the RSSHub Radar browser extension. + +The source should be an array of strings. For example, if the source for `GitHub Repo Issues` is `/:user/:repo`, it means that when you visit `https://github.com/DIYgod/RSSHub`, which matches the `github.com/:user/:repo` pattern, the parameters for this URL will be: `{user: 'DIYgod', repo: 'RSSHub'}`. The browser extension uses these parameters to create an RSSHub subscription address based on the `target` field. + +::: warning Warning +If the value you want to extract is in the URL search parameters or URL hash, use target as a function instead of the `source` field. Also, remember that the `source` field only matches the URL path and not any other parts of the URL. +::: + +You can use the `*` symbol to perform wildcard matching. Note that the syntax here is not the same as the [path-to-regexp](https://github.com/pillarjs/path-to-regexp). For instance, `/:user/:repo/*` will match both `https://github.com/DIYgod/RSSHub/issues` and `https://github.com/DIYgod/RSSHub/issues/1234`. If you want to name the matching result, you can place the variable name after the `*` symbol. For example, `/user/:repo/*path`, whereby path will be `issues` and `issues/1234` in the above scenario. + +### `target` + +The target field is *optional* and is used to generate an RSSHub subscription address. It accepts both a string or a function. If you don't want to create an RSSHub subscription address, leave this field empty. + +For the `GitHub Repo Issues` example, the corresponding route path in the RSSHub documentation is `/github/issue/:user/:repo`. + +After matching the `user` with `DIYgod` and `repo` with `RSSHub` in the source path, the `:user` in the RSSHub route path will be replaced with `DIYgod`, and `:repo` will be replaced with `RSSHub`, resulting in `/github/issue/DIYgod/RSSHub`. + +#### `target` as a function + +In some cases, the source path may not match the desired parameters for an RSSHub route. In these situations, we can use the `target` field as a function with `params`, `url`, and `document` parameters. + +The `params` parameter contains the parameters matched by the `source` field, while the `url` parameter is the current web page URL string, and the `document` parameter is the [document interface](https://developer.mozilla.org/docs/Web/API/document). + +It is essential to note that the `target` method runs in a sandbox, and any changes made to `document` will not be reflected in the web page. + +Here are two examples of how to use the `target` field as a function: + + + + +```js{9} +module.exports = { + 'github.com': { + _name: 'GitHub', + '.': [ + { + title: 'Repo Issues', + docs: 'https://docs.rsshub.app/en/programming.html#github', + source: ['/:user/:repo/issues/:id', '/:user/:repo/issues', '/:user/:repo'], + target: (params) => `/github/issue/${params.user}/${params.repo}`, + }, + ], + }, +}; +``` + + + + +```js{9} +module.exports = { + 'github.com': { + _name: 'GitHub', + '.': [ + { + title: 'Repo Issues', + docs: 'https://docs.rsshub.app/en/programming.html#github', + source: ['/:user/:repo'], + target: (_, url) => `/github/issue${new URL(url).pathname}` + }, + ], + }, +}; +``` + + + + +Both the above examples will return the same RSSHub subscription address as the [first example](/en/joinus/new-radar.html#code-the-rule). + +### RSSBud + +[RSSBud](https://github.com/Cay-Zhang/RSSBud) supports RSSHub Radar rules and will also be updated automatically, but please note that: + +- Use `'.'` subdomain allows RSSBud to support common mobile domains such as `m` / `mobile` +- Use `document` in `target` does not apply to RSSBud: RSSBud is not a browser extension, it only fetches and analyzes the URL of a website, it cannot run JavaScript + +### Update the Documentation + +As mentioned earlier in [Other components](/en/joinus/new-rss/add-docs.html#documentation-examples-other-components), adding `radar="1"` in the RSSHub docs will show a `Support browser extension` badge. If the rule is also compatible with RSSBud, adding `rssbud="1"` will show a `Support RSSBud` badge. + +## Debugging Radar Rules + +You can debug your radar rules in the RSSHub Radar extension settings of your browser. First, open the settings and switch to the "List of rules" tab. Then scroll down to the bottom of the page and you will see a text field. Here, you can replace the old rules with your new rules for debugging. + +If you are worried about losing the original RSSHub radar, don't be. It will be restored if you click the "Update Now" button in the settings page. + +Here's an example radar rule that you can play with: + +```js +({ + 'github.com': { + _name: 'GitHub', + '.': [ + { + title: 'Repo Issues', + docs: 'https://docs.rsshub.app/en/programming.html#github', + source: ['/:user/:repo/issues/:id', '/:user/:repo/issues', '/:user/:repo'], + target: '/github/issue/:user/:repo', + }, + ], + }, +}) +``` + +::: details Extra examples + +```js +({ + 'bilibili.com': { + _name: 'bilibili', + www: [ + { + title: '分区视频', + docs: 'https://docs.rsshub.app/social-media.html#bilibili', + source: '/v/*tpath', + target: (params) => { + let tid; + switch (params.tpath) { + case 'douga/mad': + tid = '24'; + break; + default: + return false; + } + return `/bilibili/partion/${tid}`; + }, + }, + ], + }, + 'twitter.com': { + _name: 'Twitter', + '.': [ + { + // for twitter.com + title: '用户时间线', + docs: 'https://docs.rsshub.app/social-media.html#twitter', + source: '/:id', + target: (params) => { + if (params.id !== 'home') { + return '/twitter/user/:id'; + } + }, + }, + ], + }, + 'pixiv.net': { + _name: 'Pixiv', + www: [ + { + title: '用户收藏', + docs: 'https://docs.rsshub.app/social-media.html#pixiv', + source: '/bookmark.php', + target: (params, url) => `/pixiv/user/bookmarks/${new URL(url).searchParams.get('id')}`, + }, + ], + }, + 'weibo.com': { + _name: '微博', + '.': [ + { + title: '博主', + docs: 'https://docs.rsshub.app/social-media.html#%E5%BE%AE%E5%8D%9A', + source: ['/u/:id', '/:id'], + target: (params, url, document) => { + const uid = document && document.documentElement.innerHTML.match(/\$CONFIG\['oid']='(\d+)'/)[1]; + return uid ? `/weibo/user/${uid}` : ''; + }, + }, + ], + }, +}) +``` + +::: diff --git a/docs/en/joinus/new-rss/add-docs.md b/docs/en/joinus/new-rss/add-docs.md new file mode 100644 index 00000000000000..435c4cf5594cdb --- /dev/null +++ b/docs/en/joinus/new-rss/add-docs.md @@ -0,0 +1,182 @@ +--- +sidebarDepth: 2 +--- + +# Add documentation + +Now that we have completed the code, it's time to add the documentation for your route. Open the appropriate file in the [documentation (/docs/en/)](https://github.com/DIYgod/RSSHub/blob/master/docs/en), which in this example is `docs/en/programming.md`. You can preview the documentation in real-time by running the following command: + + + + +```bash +yarn docs:dev +``` + + + + +```bash +npm run docs:dev +``` + + + + +The documentation is written in Markdown and rendered with [VuePress v1](https://v1.vuepress.vuejs.org). + +To add documentation to your route, use Vue components. They work like HTML tags. The following are the most commonly used components: + +- `author`: The route maintainer(s), separated by a single space. It should be the same as [`maintainer.js`](/en/joinus/new-rss/before-start.html#understand-the-basics-maintainer-js) +- `example`: The route example, with a leading `/` +- `path`: The route path, which should be the same as the key in [`maintainer.js`](/en/joinus/new-rss/before-start.html#understand-the-basics-maintainer-js) with the namespace. In the above example, it is `/github/issue/:user/:repo?` +- `:paramsDesc`: The route parameter description, in an array of strings that support Markdown. + - The description **must** follow the order in which they appear in the path. + - The number of description should match with the number of parameters in `path`. If you miss a description, the build will fail. + - Route parameters ending with `?`, `*` or `+` will be automatically marked as `optional`, `zero or more` or `one or more`, respectively. + - Route parameters without a suffix are marked as `required` + - There's no need to explicitly mention the necessity of path parameters again. + - If a parameter is optional, make sure to mention the default value. + +## Documentation examples + +### Repo Issues (No parameter) + +```vue + +``` + +--- + + + +--- + +### Repo Issues (Multiple parameters) + +```vue + +``` + +--- + + + +--- + +### Keyword (Description with table) + +```vue + + +| only not R18 | only R18 | no filter | +| ------------ | -------- | -------------- | +| safe | r18 | empty or other | + + +``` + +--- + + + +| only not R18 | only R18 | no filter | +| ------------ | -------- | -------------- | +| safe | r18 | empty or other | + + + +--- + +### Custom containers + +If you'd like to provide additional information about a particular route, you can use these custom containers: + +```md +::: tip Tips title +This is a tip. +::: + +::: warning Warning title +This is a warning. +::: + +::: danger Danger title +This is a dangerous warning. +::: + +::: details Details title +This is a details block. +::: +``` + +--- + +::: tip Tips title +This is a tip. +::: + +::: warning Warning title +This is a warning. +::: + +::: danger Danger title +This is a dangerous warning. +::: + +::: details Details title +This is a details block. +::: + +--- + +### Other components + +In addition to the route components, there are several other components you can use to provide more information about your route: + +- `anticrawler`: set to `1` if the target website has an anti-crawler mechanism. +- `puppeteer`: set to `1` if the feed uses puppeteer. +- `radar`: set to `1` if the feed has a radar rule. +- `rssbud`: set to `1` if the radar rule is also compatible with RSSBud +- `selfhost`: set to `1` if the feed requires extra configuration through environment variables. +- `supportBT`: set to `1` if the feed supports BitTorrent. +- `supportPodcast`: set to `1` if the feed supports podcasts. +- `supportScihub`: set to `1` if the feed supports Sci-Hub. + +By using these components, you can provide valuable information to users and make it easier for them to understand and use your route. Adding these components to your route documentation will add a badge in front of it. + +```vue + +``` + +--- + + + +--- + +## Other things to keep in mind + +- When documenting a route, use a level 3 heading (`###`). If the route documentation doesn't have a main section heading, add a level 2 heading (`##`). +- Leave a blank line between each heading and the following content. This will help ensure that your documentation can be built successfully. +- If the documentation contains a large table, it is suggested to put it inside a [details container](/en/joinus/new-rss/add-docs.html#documentation-examples-custom-containers) +- Components can be written in two ways: as a self-closing tag (``) or as a pair of tags (`...`). +- **Remember to close the tag!** +- Don't forget to run the following command to check and format your code before committing and submitting a merge request: + + + + +```bash +yarn format +``` + + + + +```bash +npm run format +``` + + + diff --git a/docs/en/joinus/new-rss/before-start.md b/docs/en/joinus/new-rss/before-start.md new file mode 100644 index 00000000000000..d7d05a44f28218 --- /dev/null +++ b/docs/en/joinus/new-rss/before-start.md @@ -0,0 +1,153 @@ +--- +sidebarDepth: 2 +--- +# Just before you start + +In this tutorial, we will walk you through the process of creating an RSS feed for [GitHub Repo Issues](/en/programming.html#github-repo-issues) as an example. + +## Install dependencies + +Before you start, you need to install the dependencies for RSSHub. You can do this by running the following command in the root directory of RSSHub: + + + + +```bash +yarn +``` + + + + +```bash +npm install +``` + + + + +## Start debugging + +Once you have successfully installed the dependencies, you can start debugging RSSHub by running the following command: + + + + +```bash +yarn dev +``` + + + + +```bash +npm run dev +``` + + + + +Make sure to keep an eye on the console output for any error messages or other useful information that can help you diagnose and resolve issues. Additionally, don't hesitate to consult the RSSHub documentation or seek help from the community if you encounter any difficulties. + +To view the result of your changes, open `http://localhost:1200` in your browser. You'll be able to see the changes you made to the code automatically reflected in the browser. + +## Follow the Script Standard + +It's important to ensure that all new RSS routes adhere to the [Script Standard](/en/joinus/script-standard.html). Failure to comply with this standard may result in your Pull Request not being merged in a reasonable timeframe. + +The [Script Standard](/en/joinus/script-standard.html) provides guidelines for creating high-quality and reliable source code. By following these guidelines, you can ensure that your RSS feed works as intended and is easy for other community maintainers to read. + +Before submitting your Pull Request, make sure to carefully review the [Script Standard](/en/joinus/script-standard.html) and ensure that your code meets all of the requirements. This will help to expedite the review process. + +## Create a namespace + +The first step in creating a new RSS route is to create a namespace. The namespace should be the **same** as the second level domain name of the main website for which you are creating the RSS feed. For example, if you are creating an RSS feed for , the second level domain name is `github`. Therefore, you should create a folder called `github` under `lib/v2` to serve as the namespace for your RSS route. + +::: tip Tips +When creating a namespace, it's important to avoid creating multiple namespaces for variations of the same domain. For example, if you are creating an RSS feed for both `yahoo.co.jp` and `yahoo.com`, you should stick with a single namespace `yahoo`, rather than creating multiple namespaces like `yahoo-jp`, `yahoojp`, `yahoo.jp`, `jp.yahoo`, `yahoocojp`, etc. +::: + +## Understand the Basics + +### router.js + +Once you have created the namespace for your RSS route, the next step is to register it in the `router.js` + +For example, if you are creating an RSS feed for [GitHub Repo Issues](/en/programming.html#github-repo-issues) and suppose you want users to enter a GitHub username and a repository name, and fall back to `RSSHub` if they don't enter the repository name, you can register your new RSS route in `github/router.js` using the following code: + + + + +```js{2} +module.exports = (router) => { + router.get('/issue/:user/:repo?', require('./issue')); +}; +``` + + + + +```js{2} +module.exports = function (router) { + router.get('/issue/:user/:repo?', require('./issue')); +}; +``` + + + + +When registering your new RSS route in `router.js`, you can define the route path and specify the corresponding function to be executed. In the code above, the `router.get()` method is used to specify the HTTP method and the path of the new RSS route. The first parameter of `router.get()` is the route path using [path-to-regexp](https://github.com/pillarjs/path-to-regexp) syntax. The second parameter is the exported function from your new RSS rule, `issue.js`. Note that you can omit the `.js` extension. + +In the example above, `issue` is an exact match, `:user` is a required parameter, and `:repo?` is an optional parameter. The `?` after `:repo` means that the parameter is optional. If the user does not enter repo, it will fall back to whatever is specified in your code (in this case, `RSSHub`). + +Once you have defined the route path, you can retrieve the value of the parameters from the `ctx.params` object. For example, if the user visits `/github/issue/DIYgod/RSSHub`, you can get the value of `user` and `repo` from `ctx.params.user` and `ctx.params.repo`, respectively. For example, if an user visits `/github/issue/DIYgod/RSSHub`, `ctx.params.user` and `ctx.params.repo` which will be `DIYgod` and `RSSHub`. + +**Note that the type of the value will be either `String` or `undefined`**. + +You can use the `*` or `+` symbols to match the rest of the path, like `/some/path/:variable*`. Note that `*` and `+` mean "zero or more" and "one or more", respectively. You can also use patterns like `/some/path/:variable(\\d+)?` or even RegExp. + +::: tip Tips +For more advanced usage of `router`, see the [@koa/router API Reference](https://github.com/koajs/router/blob/master/API.md). +::: + +### maintainer.js + +This file is used to store information about the maintainer of the RSS routes. You can add your GitHub username to the value array. Note that the key here should exactly match the path in `router.js` : + +```js{2} +module.exports = { + '/issue/:user/:repo?': ['DIYgod'], +}; +``` + +The `maintainer.js` file is useful for keeping track of who is responsible for maintaining an RSS route. When a user encounters an issue with your RSS route, they can reach out to the maintainers listed in this file. + +### `templates` folder + +The `templates` folder contains templates for your new RSS route. You only need this folder if you want to render custom HTML content instead of using the original website's HTML content. If you don't need to render any custom HTML content, you can skip this folder. + +Each template file should have a `.art` file extension. + +### radar.js + +The `radar.js` file helps users subscribe to your new RSS route when they use [RSSHub Radar](https://github.com/DIYgod/RSSHub-Radar) or other software that is compatible with its format. We'll cover more about this in a later section. + +### Your new RSS route `issue.js` + +Now you can [start writing](/en/joinus/new-rss/start-code.html) your new RSS route. + +## Acquire Data + +To acquire data for your new RSS route, you will typically make HTTP requests using [got](https://github.com/sindresorhus/got) to an API or webpage. In some cases, you may need to use [puppeteer](https://github.com/puppeteer/puppeteer) to simulate a browser and render a webpage in order to acquire data. + +The data you retrieve will typically be in JSON or HTML format. If you're working with HTML, you can use [cheerio](https://github.com/cheeriojs/cheerio) for further processing. + +Here's a list of recommended data collection methods, ordered by preference: + +1. **Via API**: This is the most recommended way to retrieve data, as APIs are usually more stable and faster than extracting data from an HTML webpage. + +2. **Via HTML webpage using got**: If an API is not available, you can try to retrieve data from the HTML webpage. This is the method you'll use most of the time. Note that if the HTML contains embedded JSON, you should use that instead of the rest of the HTML elements. + +3. **Common Configured Route**: The Common Configured Route is a special route that can easily generate RSS by reading JSON data through cheerio (CSS selectors and jQuery functions). + +4. **Puppeteer**: In some rare cases, you might need to use puppeteer to simulate a browser and retrieve data. This is the least recommended method and should only be used if absolutely necessary, such as when the webpage is blocked behind a browser integrity check or heavily encrypted. If possible, it's best to use other methods such as API requests or retrieving data from the HTML page directly using [got](https://github.com/sindresorhus/got) or [cheerio](https://github.com/cheeriojs/cheerio). diff --git a/docs/en/joinus/new-rss/prerequisites.md b/docs/en/joinus/new-rss/prerequisites.md new file mode 100644 index 00000000000000..974164e409a7a1 --- /dev/null +++ b/docs/en/joinus/new-rss/prerequisites.md @@ -0,0 +1,39 @@ +# Prerequisites + +Before you begin, it is important that your development environment set up properly. + +## Install Node.js + +To be able to write new RSS rules, you must first install Node.js first. RSSHub uses Node.js to run its code and create RSS feeds and requires Node v16 or above. You can download the latest LTS version of Node.js from [here](https://nodejs.org/en/download). + +On Windows, you can simply download the installer and follow the steps from the installer. Remember to check the option to install **Tools for Native Modules** as well. + +On macOS, you can either download the installer from the Node.js website or use [Homebrew](https://brew.sh) to install Node.js with the command `brew install node`. + +On Linux, you can refer to [this page](https://nodejs.org/en/download/package-manager) to decide how to install Node.js. + +## Install a code editor + +To write code, you need a code editor. If you already have one, you can skip this section. If you don't have one, you can choose one from the following list: + +- [Visual Studio Code](https://code.visualstudio.com) +- [WebStorm](https://www.jetbrains.com/webstorm) +- [Neovim](https://neovim.io) +- [Sublime Text](https://www.sublimetext.com) + +To speed up the development process and make it easier to keep your code clean, you can install some appropriate extensions to the code editor of your choice. In the latter part of this guide, we will use Visual Studio Code as an example, you can install the following extensions: + +- [Art Template Helper](https://marketplace.visualstudio.com/items?itemName=ZihanLi.at-helper)(provides syntax highlighting for a template engine used by RSSHub) +- [EditorConfig for VS Code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig)(maintain consistent coding styles across different editors and IDEs) +- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)(identify and fix common errors in your code) +- [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)(formats your code to make it more readable and consistent) + +### Cloud hosted development environment + +If you don't want to install Node.js and a code editor on your computer, you can use a cloud-hosted development environment. You may use [GitHub Codespaces](https://codespace.new/) or [Gitpod](https://www.gitpod.io). Just click one of the buttons below to start a new workspace: + +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=master&repo=127769231&machine=basicLinux32gb&devcontainer_path=.devcontainer%2Fdevcontainer.json&location=SouthEastAsia) + +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/DIYgod/RSSHub) + +For more information about how to use [GitHub Codespaces](https://codespace.new/) or [Gitpod](https://www.gitpod.io/) , see [GitHub's documentation](https://docs.github.com/codespaces) and [Gitpod's documentation](https://www.gitpod.io/docs/). diff --git a/docs/en/joinus/new-rss/start-code.md b/docs/en/joinus/new-rss/start-code.md new file mode 100644 index 00000000000000..d2105b81bb1624 --- /dev/null +++ b/docs/en/joinus/new-rss/start-code.md @@ -0,0 +1,769 @@ +--- +sidebarDepth: 2 +--- +# Create Your Own RSSHub Route + +As mentioned earlier, we will create an RSS feed for [GitHub Repo Issues](/en/programming.html#github-repo-issues) as an example. We will show all four data collection methods mentioned: + +1. [Via API](#via-api) +2. [Via HTML web page using got](#via-html-web-page-using-got) +3. [Using the Common Configured Route](#using-the-common-configured-route) +4. [Using puppeteer](#using-puppeteer) + +## Via API + +### Check the API documentation + +Different sites have different APIs. You can check the API documentation for the site you want to create an RSS feed for. In this case, we will use the [GitHub Issues API](https://docs.github.com/en/rest/issues/issues#list-repository-issues). + +### Create the main file + +Open your code editor and create a new file. Since we are going to create an RSS feed for GitHub issues, it is suggested that you save the file as `issue.js`, but you can name it whatever you like. + +Here's the basic code to get you started: + + + + +```js +// Import the necessary modules +const got = require('@/utils/got'); // a customised got +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + // Your logic here + + ctx.state.data = { + // Your RSS output here + }; +}; +``` + + + + +### Retrieving user input + +As mentioned earlier, we need to retrieve the GitHub username and repository name from user input. The repository name should default to `RSSHub` if it is not provided in the request URL. Here's how you can do it: + + + + +```js{2} +module.exports = async (ctx) => { + const { user, repo = 'RSSHub' } = ctx.params; + + ctx.state.data = { + // Your RSS output here + }; +}; +``` + + + + +```js{2,3} +module.exports = async (ctx) => { + const user = ctx.params.user; + const repo = ctx.params.repo ?? 'RSSHub'; + + ctx.state.data = { + // Your RSS output here + }; +}; +``` + + + + +Both of these code snippets do the same thing. The first one uses object destructuring to assign the `user` and `repo` variables, while the second one uses traditional assignment with a nullish coalescing operator to assign the `repo` variable a default value of `RSSHub` if it is not provided in the request URL. + +### Getting data from the API + +After we have the user input, we can use it to make a request to the API. In most cases, you will need to use `got` from `@/utils/got` (a customized got wrapper) to make HTTP requests. For more information, please refer to the [got documentation](https://github.com/sindresorhus/got/tree/v11#usage). + + + + +```js{3-16} +module.exports = async (ctx) => { + const { user, repo = 'RSSHub' } = ctx.params; + // Send an HTTP GET request to the API + // and destruct the data object returned by the request + const { data } = await got(`https://api.github.com/repos/${user}/${repo}/issues`, { + headers: { + // This example uses HTML for simplicity instead of the + // recommended 'application/vnd.github+json', which returns + // Markdown and requires additional processing + accept: 'application/vnd.github.html+json', + }, + searchParams: { + // This allows users to set the number of feed items they want + per_page: ctx.query.limit ? parseInt(ctx.query.limit, 10) : 30, + }, + }); + + ctx.state.data = { + // Your RSS output here + }; +}; +``` + + + + +```js{4-14} +module.exports = async (ctx) => { + const user = ctx.params.user; + const repo = ctx.params.repo ?? 'RSSHub'; + // Send an HTTP GET request to the API + const response = await got(`https://api.github.com/repos/${user}/${repo}/issues`, { + headers: { + accept: 'application/vnd.github.html+json', + }, + searchParams: { + per_page: ctx.query.limit ? parseInt(ctx.query.limit, 10) : 30, + }, + }); + // response.data is the data object returned by the above request + const data = response.data; + + ctx.state.data = { + // Your RSS output here + }; +}; +``` + + + + +### Outputting the RSS + +Once we have retrieved the data from the API, we need to process it further to generate an RSS feed that conforms to the RSS specification. Specifically, we need to extract the channel title, channel link, item title, item link, item description, and item publication date. + +To do this, we can assign the relevant data to the `ctx.state.data` object, and RSSHub's middleware will take care of the rest. + +Here is the final code that you should have: + + + + +```js{16-30,32-39} +const got = require('@/utils/got'); +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + const { user, repo = 'RSSHub' } = ctx.params; + + const { data } = await got(`https://api.github.com/repos/${user}/${repo}/issues`, { + headers: { + accept: 'application/vnd.github.html+json', + }, + searchParams: { + per_page: ctx.query.limit ? parseInt(ctx.query.limit, 10) : 30, + }, + }); + + // extract the relevant data from the API response + const items = data.map((item) => ({ + // item title + title: item.title, + // item link + link: item.html_url, + // item description + description: item.body_html, + // item publish date or time + pubDate: parseDate(item.created_at), + // item author, if available + author: item.user.login, + // item category, if available + category: item.labels.map((label) => label.name), + })); + + ctx.state.data = { + // channel title + title: `${user}/${repo} issues`, + // channel link + link: `https://github.com/${user}/${repo}/issues`, + // each feed item + item: items, + }; +}; +``` + + + + +```js{16-36} +const got = require('@/utils/got'); +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + const { user, repo = 'RSSHub' } = ctx.params; + + const { data } = await got(`https://api.github.com/repos/${user}/${repo}/issues`, { + headers: { + accept: 'application/vnd.github.html+json', + }, + searchParams: { + per_page: ctx.query.limit ? parseInt(ctx.query.limit, 10) : 30, + }, + }); + + ctx.state.data = { + // channel title + title: `${user}/${repo} issues`, + // channel link + link: `https://github.com/${user}/${repo}/issues`, + // iterate through all leaf objects + item: data.map((item) => ({ + // item title + title: item.title, + // item link + link: item.html_url, + // item description + description: item.body_html, + // item publish date or time + pubDate: parseDate(item.created_at), + // item author, if available + author: item.user.login, + // item category, if available + category: item.labels.map((label) => label.name), + })); + }; +}; +``` + + + + +## Via HTML web page using got + +### Creat the main file + +To start, open your code editor and create a new file. Since we are going to create an RSS feed for GitHub issues, it is suggested that you save the file as `issue.js`. However, you can also name it whatever you like. + +Here's the basic code to get you started: + +```js +// Require necessary modules +const got = require('@/utils/got'); // a customised got +const cheerio = require('cheerio'); // an HTML parser with a jQuery-like API +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + // Your logic here + + ctx.state.data = { + // Your RSS output here + }; +}; +``` + +The `parseDate` function is a utility function provided by RSSHub that we will use to parse dates later in the code. + +You will add your own code to extract data from the HTML document, process it, and output it in RSS format. We will cover the details of this process in the next steps. + +### Retrieving user input + +As mentioned before, we want users to enter a GitHub username and a repository name, and fall back to `RSSHub` if they don't enter the repository name in the request URL. + +```js{2-3} +module.exports = async (ctx) => { + // Retrieve user and repository name from the URL parameters + const { user, repo = 'RSSHub' } = ctx.params; + + ctx.state.data = { + // Your RSS output here + }; +}; +``` + +In this code, `user` will be set to the value of `user` parameter, and `repo` will be set to the value of `repo` parameter if it exists, and `RSSHub` otherwise. + +### Getting data from the web page + +After receiving the user input, we need to make a request to the web page to retrieve the information we need. In most cases, we'll use `got` from `@/utils/got` (a customized [got](https://www.npmjs.com/package/got) wrapper) to make HTTP requests. You can find more information on how to use got in the [got documentation](https://github.com/sindresorhus/got/tree/v11#usage). + +To begin, we'll make an HTTP GET request to the API and load the HTML response into Cheerio, a library that helps us parse and manipulate HTML. + +```js{5-6} + const baseUrl = 'https://github.com'; + const { user, repo = 'RSSHub' } = ctx.params; + + // Note that the ".data" property contains the full HTML source of the target page returned by the request + const { data: response } = await got(`${baseUrl}/${user}/${repo}/issues`); + const $ = cheerio.load(response); +``` + +Next, we'll use Cheerio selectors to select the relevant HTML elements, parse the data we need, and convert it into an array. + +```js{3-21} + // We use a Cheerio selector to select all 'div' elements with the class name 'js-navigation-container' + // that contain child elements with the class name 'flex-auto'. + const item = $('div.js-navigation-container .flex-auto') + // We use the `toArray()` method to retrieve all the DOM elements selected as an array. + .toArray() + // We use the `map()` method to traverse the array and parse the data we need from each element. + .map((item) => { + item = $(item); + const a = item.find('a').first(); + return { + title: a.text(), + // We need an absolute URL for `link`, but `a.attr('href')` returns a relative URL. + link: `${baseUrl}${a.attr('href')}`, + pubDate: parseDate(item.find('relative-time').attr('datetime')), + author: item.find('.opened-by a').text(), + category: item + .find('a[id^=label]') + .toArray() + .map((item) => $(item).text()), + }; + }); + + ctx.state.data = { + // Your RSS output here + }; +``` + +### Outputting the RSS + +Once we have the data from the web page, we need to further process it to generate RSS in accordance with the RSS specification. Mainly, we need the channel title, channel link, item title, item link, item description, and item publication date. + +Assign them to the `ctx.state.data` object, and RSSHub's middleware will take care of the rest. + +Here's an example code: + +```js{29-36} +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + const baseUrl = 'https://github.com'; + const { user, repo = 'RSSHub' } = ctx.params; + + const { data: response } = await got(`${baseUrl}/${user}/${repo}/issues`); + const $ = cheerio.load(response); + + const item = $('div.js-navigation-container .flex-auto') + .toArray() + .map((item) => { + item = $(item); + const a = item.find('a').first(); + return { + title: a.text(), + link: `${baseUrl}${a.attr('href')}`, + pubDate: parseDate(item.find('relative-time').attr('datetime')), + author: item.find('.opened-by a').text(), + category: item + .find('a[id^=label]') + .toArray() + .map((item) => $(item).text()), + }; + }); + + ctx.state.data = { + // channel title + title: `${user}/${repo} issues`, + // channel link + link: `${baseUrl}/${user}/${repo}/issues`, + // each feed item + item: items, + }; +}; +``` + +### Better Reading Experience + +The previous code provides only part of the information for each feed item. To provide a better reading experience, we can add the full article to each feed item, in this case the issue body. + +Here's the updated code: + +```js{12,29-43,48} +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + const baseUrl = 'https://github.com'; + const { user, repo = 'RSSHub' } = ctx.params; + + const { data: response } = await got(`${baseUrl}/${user}/${repo}/issues`); + const $ = cheerio.load(response); + + const list = $('div.js-navigation-container .flex-auto') + .toArray() + .map((item) => { + item = $(item); + const a = item.find('a').first(); + return { + title: a.text(), + link: `${baseUrl}${a.attr('href')}`, + pubDate: parseDate(item.find('relative-time').attr('datetime')), + author: item.find('.opened-by a').text(), + category: item + .find('a[id^=label]') + .toArray() + .map((item) => $(item).text()), + }; + }); + + const items = await Promise.all( + list.map((item) => + ctx.cache.tryGet(item.link, async () => { + const { data: response } = await got(item.link); + const $ = cheerio.load(response); + + // Select the first element with the class name 'comment-body' + item.description = $('.comment-body').first().html(); + + // Every property of a list item defined above is reused here + // and we add a new property 'description' + return item; + }) + ) + ); + + ctx.state.data = { + title: `${user}/${repo} issues`, + link: `https://github.com/${user}/${repo}/issues`, + item: items, + }; +}; + +``` + +Now the RSS feed will have a similar reading experience to the original website. + +::: tip Note +Note that in the previous section, we only needed to send one HTTP request using an API to get all the data we needed. However, in this section, we need to send `1 + n` HTTP requests, where `n` is the number of feed items in the list from the first request. + +Some websites may not want to receive too many requests in a short amount of time, which can cause them to return an error message like `429 Too Many Requests`. +::: + +## Using the common configured route + +### Create the main file + +First, we need a few data: + +1. The RSS source link +2. The data source link +3. The RSS feed title (not the title of individual items) + +Open your code editor and create a new file. Since we're going to create an RSS feed for GitHub issues, it's suggested that you save the file as `issue.js`, but you can name it whatever you like. + +Here's some basic code to get you started: + +```js +// Import necessary modules +const buildData = require('@/utils/common-config'); + +module.exports = async (ctx) => { + ctx.state.data = await buildData({ + link: '', // The RSS source link + url: '', // The data source link + // Variables can be used here, such as %xxx% will be parsed into + // variables with values of the same name under **params** + title: '%title%', + params: { + title: '', // Additional title + }, + }); +}; +``` + +Our RSS feed currently lacks content. The `item` must be set to add the content. Here's an example: + +```js{15-22} +const buildData = require('@/utils/common-config'); + +module.exports = async (ctx) => { + const { user, repo = 'RSSHub' } = ctx.params; + const link = `https://github.com/${user}/${repo}/issues`; + + ctx.state.data = await buildData({ + link, + url: link, + title: `${user}/${repo} issues`, // you can also use $('head title').text() + params: { + title: `${user}/${repo} issues`, + baseUrl: 'https://github.com', + }, + item: { + item: 'div.js-navigation-container .flex-auto', + // You need to use template literals if you want to use variables + title: `$('a').first().text() + ' - %title%'`, // Only supports js statements like $().xxx() + link: `'%baseUrl%' + $('a').first().attr('href')`, // .text() means get the text of the element + // description: ..., we don't have description for now + pubDate: `parseDate($('relative-time').attr('datetime'))`, + }, + }); +}; +``` + +You'll notice that the code is similar to the [Obtaining data from the webpage](#via-html-web-page-using-got-getting-data-from-the-web-page) section above. However, this RSS feed doesn't contain the full article of the issue. + +### Retrieving full articles + +To get the full article of each issue, you need to add a few more lines of code. Here is an example: + +```js{2-3,25-34} +const buildData = require('@/utils/common-config'); +const got = require('@/utils/got'); +const cheerio = require('cheerio'); + +module.exports = async (ctx) => { + const { user, repo = 'RSSHub' } = ctx.params; + const link = `https://github.com/${user}/${repo}/issues`; + + ctx.state.data = await buildData({ + link, + url: link, + title: `${user}/${repo} issues`, + params: { + title: `${user}/${repo} issues`, + baseUrl: 'https://github.com', + }, + item: { + item: 'div.js-navigation-container .flex-auto', + title: `$('a').first().text() + ' - %title%'`, + link: `'%baseUrl%' + $('a').first().attr('href')`, + pubDate: `parseDate($('relative-time').attr('datetime'))`, + }, + }); + + await Promise.all( + ctx.state.data.item.map((item) => + ctx.cache.tryGet(item.link, async () => { + const { data: resonse } = await got(item.link); + const $ = cheerio.load(resonse); + item.description = $('.comment-body').first().html(); + return item; + }) + ) + ); +}; +``` + +You can see that the above code is very similar to the [previous section](#via-html-web-page-using-got-better-reading-experience) which retrieves full articles by adding a few more lines of code. It is recommended that you use the method in the [previous section](#via-html-web-page-using-got-better-reading-experience) whenever possible, as it is more flexible than using `@/utils/common-config`. + +## Using puppeteer + +Using puppeteer is another approach to obtain data from websites. However, it is recommended that you try the [above methods](#via-html-web-page-using-got) first. It is also recommended that you read [via HTML web page using got](#via-html-web-page-using-got) first since this section is an extension of the previous section and will not explain some basic concepts. + +### Creat the main file + +To get started with puppeteer, create a new file in your code editor and save it with an appropriate name, such as `issue.js`. Then, require the necessary modules and set up the basic structure of the function: + +```js +// Require some useful modules +const cheerio = require('cheerio'); // an HTML parser with a jQuery-like API +const { parseDate } = require('@/utils/parse-date'); +const logger = require('@/utils/logger'); + +module.exports = async (ctx) => { + // Your logic here + + ctx.state.data = { + // Your RSS output here + }; +}; +``` + +### Replace got with puppeteer + +Now, we will be using `puppeteer` instead of `got` to retrieve data from the web page. + + + + +```js{9-33,39-40} +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); +const logger = require('@/utils/logger'); + +module.exports = async (ctx) => { + const baseUrl = 'https://github.com'; + const { user, repo = 'RSSHub' } = ctx.params; + + // require puppeteer utility class and initialise a browser instance + const browser = await require('@/utils/puppeteer')(); + // open a new tab + const page = await browser.newPage(); + // intercept all requests + await page.setRequestInterception(true); + // only allow certain types of requests to proceed + page.on('request', (request) => { + // in this case, we only allow document requests to proceed + request.resourceType() === 'document' ? request.continue() : request.abort(); + }); + // visit the target link + const link = `${baseUrl}/${user}/${repo}/issues`; + // got requests will be logged automatically + // but puppeteer requests are not + // so we need to log them manually + logger.debug(`Requesting ${link}`); + await page.goto(link, { + // specify how long to wait for the page to load + waitUntil: 'domcontentloaded', + }); + // retrieve the HTML content of the page + const response = await page.content(); + // close the tab + page.close(); + + const $ = cheerio.load(response); + + // const item = ...; + + // don't forget to close the browser instance at the end of the function + browser.close(); + + ctx.state.data = { + // Your RSS output here + }; +} +``` + + + + +```js{9} +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + const baseUrl = 'https://github.com'; + const { user, repo = 'RSSHub' } = ctx.params; + + const { data: response } = await got(`${baseUrl}/${user}/${repo}/issues`); + const $ = cheerio.load(response); + + ctx.state.data = { + // Your RSS output here + }; +} +``` + + + + +### Retrieving full articles + +Retrieving the full articles of each issue using a new browser page is similar to the [previous section](via-html-web-page-using-got-better-reading-experience). We can use the following code: + +```js{46-60,71-72} +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); +const logger = require('@/utils/logger'); + +module.exports = async (ctx) => { + const baseUrl = 'https://github.com'; + const { user, repo = 'RSSHub' } = ctx.params; + + const browser = await require('@/utils/puppeteer')(); + const page = await browser.newPage(); + await page.setRequestInterception(true); + page.on('request', (request) => { + request.resourceType() === 'document' ? request.continue() : request.abort(); + }); + + const link = `${baseUrl}/${user}/${repo}/issues`; + logger.debug(`Requesting ${link}`); + await page.goto(link, { + waitUntil: 'domcontentloaded', + }); + const response = await page.content(); + page.close(); + + const $ = cheerio.load(response); + + const list = $('div.js-navigation-container .flex-auto') + .toArray() + .map((item) => { + item = $(item); + const a = item.find('a').first(); + return { + title: a.text(), + link: `${baseUrl}${a.attr('href')}`, + pubDate: parseDate(item.find('relative-time').attr('datetime')), + author: item.find('.opened-by a').text(), + category: item + .find('a[id^=label]') + .toArray() + .map((item) => $(item).text()), + }; + }); + + const items = await Promise.all( + list.map((item) => + ctx.cache.tryGet(item.link, async () => { + // reuse the browser instance and open a new tab + const page = await browser.newPage(); + // set up request interception to only allow document requests + await page.setRequestInterception(true); + page.on('request', (request) => { + request.resourceType() === 'document' ? request.continue() : request.abort(); + }); + + logger.debug(`Requesting ${item.link}`); + await page.goto(item.link, { + waitUntil: 'domcontentloaded', + }); + const response = await page.content(); + // close the tab after retrieving the HTML content + page.close(); + + const $ = cheerio.load(response); + + item.description = $('.comment-body').first().html(); + + return item; + }) + ) + ); + + // close the browser instance after all requests are done + browser.close(); + + ctx.state.data = { + title: `${user}/${repo} issues`, + link: `https://github.com/${user}/${repo}/issues`, + item: items, + }; +}; +``` + +### Additional Resources + +Here are some resources you can use to learn more about puppeteer: + +- [puppeteer's good ol' docs](https://github.com/puppeteer/puppeteer/blob/v15.2.0/docs/api.md) +- [puppeteer's current docs](https://pptr.dev) + +#### Intercepting requests + +When scraping web pages, you may encounter images, fonts, and other resources that you don't need. These resources can slow down the page load time and use up valuable CPU and memory resources. To avoid this, you can enable request interception in puppeteer. + +Here's how to do it: + +```js +await page.setRequestInterception(true); +page.on('request', (request) => { + request.resourceType() === 'document' ? request.continue() : request.abort(); +}); +// These two statements must be placed before page.goto() +``` + +You can find all the possible values of `request.resourceType()` [here](https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-ResourceType). When using these values in your code, make sure to use **lowercase** letters. + +#### Wait Until + +In the code above, you'll see that `waitUntil: 'domcontentloaded'` is used in the page.goto() function. This is a Puppeteer option that tells it when to consider a navigation successful. You can find all the possible values and their meanings [here](https://pptr.dev/api/puppeteer.page.goto/#remarks). + +It's worth noting that `domcontentloaded `waits for a shorter time than the default value `load`, and `networkidle0` may not be suitable for websites that keep sending background telemetry or fetching data. + +Additionally, it's important to avoid waiting for a specific timeout and instead wait for a selector to appear. Waiting for a timeout is inaccurate, as it depends on the load of the Puppeteer instance. diff --git a/docs/en/joinus/new-rss/submit-route.md b/docs/en/joinus/new-rss/submit-route.md new file mode 100644 index 00000000000000..9c7c94a7073b95 --- /dev/null +++ b/docs/en/joinus/new-rss/submit-route.md @@ -0,0 +1,122 @@ +--- +sidebarDepth: 2 +--- +# Submit your route + +Once you have finished your route, you can submit a pull request (hereafter referred to as PR) to [RSSHub](https://github.com/DIYgod/RSSHub). We use a squash merge strategy, meaning all commits in your branch will be merged into one commit on RSSHub's repository. However, keeping your commit history clean and tidy is still important. We've also provided an intuitive template for you to fill out. + +## Pull Request Template + +````md + + +## 该 PR 相关 Issue / Involved Issue + +Close # + +## 完整路由地址 / Example for the Proposed Route(s) + + + +```routes +``` + +## 新 RSS 路由检查表 / New RSS Route Checklist + +- [ ] 新的路由 New Route + - [ ] 跟随 [v2 路由规范](https://docs.rsshub.app/joinus/script-standard.html) Follows [v2 Script Standard](https://docs.rsshub.app/en/joinus/script-standard.html) +- [ ] 文档说明 Documentation + - [ ] 中文文档 CN + - [ ] 英文文档 EN +- [ ] 全文获取 fulltext + - [ ] 使用缓存 Use Cache +- [ ] 反爬/频率限制 anti-bot or rate limit? + - [ ] 如果有, 是否有对应的措施? If yes, do your code reflect this sign? +- [ ] [日期和时间](https://docs.rsshub.app/joinus/pub-date.html) [date and time](https://docs.rsshub.app/en/joinus/pub-date.html) + - [ ] 可以解析 Parsed + - [ ] 时区调整 Correct TimeZone +- [ ] 添加了新的包 New package added +- [ ] `Puppeteer` + +## 说明 / Note +```` + +### Involved Issue + +You can fill in the issue number that this PR is related to here. If there's no related issue, leave it blank. If your pull request gets merged, the related issue will be automatically closed. If you want to close multiple issues, add another `Close #` separated by a space or comma. For example, `Close #123, Close #456, Close #789` or `Close #123 Close #456 Close #789`. + +### Example for the Proposed Route(s) + +Here you can add the route(s) you're proposing to add, along with all required and optional parameters. If you want to add multiple routes, add each one in a new line. For example: + +````md +```routes +/github/issue/DIYgod +/github/issue/DIYgod/RSSHub +/github/issue/DIYgod/RSSHub-Radar +/github/issue/flutter/flutter +``` +```` + +**Do not** fill in `/github/issue/:user/:repo?` or `/issue/:user/:repo?`. + +If your changes are not related to a route, such as documentation, you can fill in `routes` section with `NOROUTE`. + +````md +```routes +NOROUTE +``` +```` + +**Do not** delete or leave the `routes` section untouched, or your pull request will be automatically closed. + +**Do not** use `NOROUTE` for route-related pull requests, or they will be automatically closed as well. + +### New RSS Route Checklist + +This checklist will help you ensure that your pull request includes all necessary components. Although you don't have to check off all items to get your PR merged, please make sure that your new route follows the [v2 Script Standard](https://docs.rsshub.app/en/joinus/script-standard.html). This is a **mandatory** requirement for all new routes. + +```md +- [ ] 新的路由 New Route +``` + +To check off an item, replace `[ ]` with `[x]`. + +```md +- [x] 新的路由 New Route +``` + +### Note + +Use this section to include any additional information or comments you'd like to share. + +## Pull Request Title + +The pull request title will be used as the commit message when your pull request is merged. Please follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) specification. + +If you are adding a new route, including all required documentation and `radar.js`, use `route` as the scope. If you are adding new radar rules only, use `radar` as the scope. + +## Response to Code Review + +Your pull request will be reviewed by both bots and RSSHub maintainers. You can check the details of the automated checks by clicking on the `Details` link next to the check name. +If an RSSHub maintainer requests changes, you can commit and push your changes to your branch. The pull request will automatically update to reflect your changes. You can also incorporate feedback from the maintainer in batch by using the [suggested changes feature](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/incorporating-feedback-in-your-pull-request#applying-suggested-changes). + +## What's Next + +After your pull request is merged, a new docker image will be built. This process can take up to an hour since we are building for multiple platforms, including `linux/arm/v7`, `linux/arm64`, and `linux/amd64`, with and without bundled Chromium. diff --git a/docs/en/joinus/pub-date.md b/docs/en/joinus/pub-date.md index 0435e5d8e96037..d86a9d02581617 100644 --- a/docs/en/joinus/pub-date.md +++ b/docs/en/joinus/pub-date.md @@ -1,39 +1,44 @@ # Date Handling -When crawling a web page, the web page usually provides a date. This tutorial will illustrate how a script should properly handle that situation +When you visit a website, the website usually provides you with a date or timestamp. This tutorial will show you how to properly handle them in your code. -## No Date +## The Standard -**Do not add a date** when a website does not provide one. The `pubDate` option should be left empty. +### No Date -## Standard +- **Do not** add a date when a website does not provide one. Leave the `pubDate` field undefined. +- Parse only the date and **do not add a time** to the `pubDate` field when a website provides a date but not an accurate time. -`pubDate` must be a +The `pubDate` field must be a: -1. [Date Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) -2. **Not recommended, only use for compatible** strings that can be parsed correctly because its behavior may be inconsistent across environments, [Date.parse()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse). Please avoid using it +1. [Date Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date) +2. **Not recommended. Only use for compatibility**: Strings that can be parsed correctly because their behavior can be inconsistent across deployment environments. Use `Date.parse()` with caution. -Also, the `pubDate` passed in from the script should correspond to the time zone/time used by the **server**. For more details, see the following: +The `pubDate` passed from the route script should correspond to the time zone/time used by the server. For more details, see the following: ## Use utilities class -We recommend using [Day.js](https://github.com/iamkun/dayjs) for date processing and time zone adjustment as of now. There are two related tool classes: +We recommend using [day.js](https://github.com/iamkun/dayjs) for date processing and time zone adjustment. There are two related utility classes: -### Parse Date +### Date and Time -This is a utility class for using [Day.js](https://github.com/iamkun/dayjs). In most cases, it is possible to use it directly to get the correct `Date Object` +The RSSHub utility class includes a wrapper for [day.js](https://github.com/iamkun/dayjs) that allows you to easily parse date strings and obtain a [Date Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date) in most cases. -Please refer to Day.js GitHub description for specific parsing parameters - -```javascript +```js const { parseDate } = require('@/utils/parse-date'); +const pubDate = parseDate('2020/12/30'); +// OR const pubDate = parseDate('2020/12/30', 'YYYY/MM/DD'); ``` +:::tip Tips +You can refer to the [day.js documentation](https://day.js.org/docs/en/parse/string-format#list-of-all-available-parsing-tokens) for all available date formats. +::: + If you need to parse a relative date, use `parseRelativeDate`. -```javascript +```js const { parseRelativeDate } = require('@/utils/parse-date'); const pubDate = parseRelativeDate('2 days ago'); @@ -42,14 +47,16 @@ const pubDate = parseRelativeDate('day before yesterday 15:36'); ### Timezone -Some websites will not convert the time zone according to the location of a visitor. The time obtained will be the local time of the website, which may not be suitable for all RSS subscribers. In this case, you should specify the time zone manually: +When parsing dates from websites, it's important to consider time zones. Some websites may not convert the time zone according to the visitor's location, resulting in a date that doesn't accurately reflect the user's local time. To avoid this issue, you can manually specify the time zone. -::: warning Warning -Now, the time will be converted to server time, which facilitates middleware processing. -::: +To manually specify the time zone in your code, use the following code: -```javascript +```js const timezone = require('@/utils/timezone'); -const pubDate = timezone(new Date(), +8); +const pubDate = timezone(parseDate('2020/12/30 13:00'), +1); ``` + +The timezone function takes two parameters: the first is the original [Date Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date), and the second is the time zone offset. The offset is specified in hours, so in this example, a time zone of UTC+1 is used. + +By doing this, the time will be converted to server time and it will facilitate middleware processing. diff --git a/docs/en/joinus/quick-start.md b/docs/en/joinus/quick-start.md index 423504881aacab..8e8c922340c6eb 100644 --- a/docs/en/joinus/quick-start.md +++ b/docs/en/joinus/quick-start.md @@ -1,609 +1,48 @@ ---- -sidebar: auto ---- - # Join Us -We welcome all pull requests. Suggestions and feedback are also welcomed [here](https://github.com/DIYgod/RSSHub/issues). +If you've found a bug or have a suggestion for improving RSSHub, we'd love to hear from you! You can submit your changes by creating a pull request. Don't worry if you're new to pull requests - we welcome contributions from developers of all experience levels. Don't know how to code? You can also help by [reporting bugs](https://github.com/DIYgod/RSSHub/issues). ## Join the discussion -1. [Telegram Group](https://t.me/rsshub) -2. [GitHub Issues](https://github.com/DIYgod/RSSHub/issues) - -## Submit new RSS rule - -Before you start writing an RSS rule, please make sure that the source site does not provide RSS. Some web pages will include a link element with type `application/atom+xml` or `application/rss+xml` in the HTML header to indicate the RSS link. - -### Debug - -First, `yarn` or `npm install` to install dependencies, then execute `yarn dev` or `npm run dev`, open `http://localhost:1200` to see the effect, and the page will refresh automatically if files are modified. - -### Add route - -First, create the corresponding route path in [/lib/v2/](https://github.com/DIYgod/RSSHub/tree/master/lib/v2) and add the route in `/lib/v2/:path/router.js` - -### Code the script - -Create a new js script in the corresponding [/lib/v2/](https://github.com/DIYgod/RSSHub/tree/master/lib/v2/) path - -#### Acquiring Data - -- Typically, the data are acquired via HTTP requests (via API or webpage) sent by [got](https://github.com/sindresorhus/got) -- Occasionally [puppeteer](https://github.com/GoogleChrome/puppeteer) is required for browser stimulation and page rendering in order to acquire the data - -- The acquired data are most likely in JSON or HTML format -- For HTML format, [cheerio](https://github.com/cheeriojs/cheerio) is used for further processing - -- Below is a list of data acquisition methods, ordered by the **「level of recommendation」** - - 1. **Acquire data via API using got** - - Example: [/lib/routes/bilibili/coin.js](https://github.com/DIYgod/RSSHub/blob/master/lib/routes/bilibili/coin.js)。 - - Acquiring data via the official API provided by the data source using got: - - ```js - // Initiate a HTTP GET request - const response = await got({ - method: 'get', - url: `https://api.bilibili.com/x/space/coin/video?vmid=${uid}&jsonp=jsonp`, - headers: { - Referer: `https://space.bilibili.com/${uid}/`, - }, - }); - - const data = response.data.data; // response.data is the data object returned from the previous request - // The object contains a nested object called data, thus response.data.data is the actual data needed here - ``` - - One of the leaf objects (response.data.data[0]): - - ```json - { - "aid": 33614333, - "videos": 2, - "tid": 20, - "tname": "宅舞", - "copyright": 1, - "pic": "http://i0.hdslb.com/bfs/archive/5649d7fe6ff7f7b431300fc1a0db80d3f174cacd.jpg", - "title": "【赤九玖】响喜乱舞【和我一起狂舞吧,团长大人(✧◡✧)】", - "pubdate": 1539259203, - "ctime": 1539249536, - "desc": "编舞出处:av31984673\n真心好喜欢这个舞和这首歌,居然恰巧被邀请跳了,感谢《苍之纪元》官方的邀请。这次cos的是游戏的新角色缪斯。然而时间有限很多地方还有很多不足。也没跳够,以后私下还会继续练习,希望能学到更多动作,也能为了有机会把它跳的更好。 \n摄影:绯山圣瞳九命猫 \n后期:炉火" - // some more data.... - } - ``` - - Processing the data further to generate objects in accordance with RSS specification, mainly title, link, description, publish time, then assign them to ctx.state.data, [produce RSS feed](#produce-rss-feed): - - ```js - ctx.state.data = { - // the source title - title: `${name} 的 bilibili 投币视频`, - // the source link - link: `https://space.bilibili.com/${uid}`, - // the source description - description: `${name} 的 bilibili 投币视频`, - // iterate through all leaf objects - item: data.map((item) => ({ - // the article title - title: item.title, - // the article content - description: `${item.desc}
`, - // the article publish time - pubDate: new Date(item.time * 1000).toUTCString(), - // the article link - link: `https://www.bilibili.com/video/av${item.aid}`, - })), - }; - - // the route is now done - ``` - - 2. **Acquire data via HTML webpage using got** - - Data have to be acquired via HTML webpage if **no API was provided**, for example: [/lib/routes/douban/explore.js](https://github.com/DIYgod/RSSHub/blob/master/lib/routes/douban/explore.js) - - Acquiring data by scrapping the HTML using got: - - ```js - // Initiate a HTTP GET request - const response = await got({ - method: 'get', - url: 'https://www.douban.com/explore', - }); - - const data = response.data; // response.data is the entire HTML source of the target page, returned from the previous request - ``` - - Parsing the HTML using cheerio: - - ```js - const $ = cheerio.load(data); // Load the HTML returned into cheerio - const list = $('div[data-item_id]'); - // use cheerio selector, select all 'div' elements with 'data-item_id' attribute, the result is an array of cheerio node objects - // use cheerio get() method to transform a cheerio node object array into a node array - - // PS: every cheerio node is a HTML DOM - // PPS: cheerio selector is almost identical to jquery selector - // Refer to cheerio docs: https://cheerio.js.org/ - ``` - - Use map to traverse the array and parse out the result of each item - - ```js - ctx.state.data = { - title: '豆瓣-浏览发现', - link: 'https://www.douban.com/explore', - item: - list && - list - .map((index, item) => { - item = $(item); - itemPicUrl = item.find('a.cover').attr('style').replace('background-image:url(', '').replace(')', ''); - return { - title: item.find('.title a').first().text(), - description: `作者:${item.find('.usr-pic a').last().text()}
描述:${item.find('.content p').text()}
`, - link: item.find('.title a').attr('href'), - }; - }) - .get(), - }; - - // the route is now done - ``` - - 3. **Acquire data via page rendering using puppeteer** - - ::: tip Tips - - This method consumes more resources and is less performant, use only when the above methods are failed to acquire data. Otherwise, your pull requests will be rejected! - - ::: - - Seldomly, data source **provides no API and the page requires rendering** to acquire data, for example: [/lib/routes/sspai/series.js](https://github.com/DIYgod/RSSHub/blob/master/lib/routes/sspai/series.js) - - ```js - // use puppeteer util class, initialise a browser instance - const browser = await require('@/utils/puppeteer')(); - // open a new page - const page = await browser.newPage(); - // access the target link - const link = 'https://sspai.com/series'; - await page.goto(link); - // render the page - const html = await page.evaluate( - () => - // process on the rendered page - document.querySelector('div.new-series-wrapper').innerHTML - ); - // shutdown the browser - browser.close(); - ``` - - Parsing the HTML using cheerio: - - ```js - const $ = cheerio.load(html); // Load the HTML returned into cheerio - const list = $('div.item'); // // use cheerio selector, select all 'div class="item"' elements, the result is an array of cheerio node objects - ``` - - Assign the value to `ctx.state.data` - - ```js - ctx.state.data = { - title: '少数派 -- 最新上架付费专栏', - link, - description: '少数派 -- 最新上架付费专栏', - item: list - .map((i, item) => ({ - // the article title - title: $(item).find('.item-title a').text().trim(), - // the article link - link: url.resolve(link, $(item).find('.item-title a').attr('href')), - // the article author - author: $(item).find('.item-author').text().trim(), - })) - .get(), // use cheerio get() method to transform a cheerio node object array into a node array - }; - - // the route is now done - - // PS: the route acts as a notifier of new articles. It does not provide access to the content behind the paywall. Thus no content was fetched - ``` - - 4. **Use general configuration routing** - - A large number of websites can generate RSS through a configuration paradigm. - - The general configuration is to generate RSS with ease by reading json data through cheerio (**CSS selector, jQuery function**) - - First, we need a few data: - - 1. RSS source link - 2. Data source link - 3. RSS title (not item title) - - ```js - const buildData = require('@/utils/common-config'); - module.exports = async (ctx) => { - ctx.state.data = await buildData({ - link: '', // RSS source link - url: '', // Data source link - title: '%title%', // Variables are used here, such as **% xxx%** will be parsed into variables with values of the same name under **params** - params: { - title: '', // RSS title - }, - }); - }; - ``` - - Our RSS does not have any content for now. The content needs to be completed by `item`. - Here is an example - - ```js - const buildData = require('@/utils/common-config'); - - module.exports = async (ctx) => { - const link = `https://www.uraaka-joshi.com/`; - ctx.state.data = await buildData({ - link, - url: link, - title: `%title%`, - params: { - title: '裏垢女子まとめ', - }, - item: { - item: '.content-main .stream .stream-item', - title: `$('.post-account-group').text() + ' - %title%'`, // Only supports js statements like $().xxx() - link: `$('.post-account-group').attr('href')`, // .text() means get the text of the element, .attr() means get the specified attribute - description: `$('.post .context').html()`, // .text() means get the text of the the html code - pubDate: `new Date($('.post-time').attr('datetime')).toUTCString()`, - guid: `new Date($('.post-time').attr('datetime')).getTime()`, - }, - }); - }; - ``` - - So far we have completed a simplest route - ---- - -#### Use Cache - -All routes have a cache, the global cache time is set in `lib/config.js`, but the content returned by some interfaces is updated less frequently. For that, you should set a longer cache time for these data like the full text of an article. - -For example, the bilibili column needs to get the full text of the article: [/lib/routes/bilibili/followings_article.js](https://github.com/DIYgod/RSSHub/blob/master/lib/routes/bilibili/followings_article.js) - -Since the full text of all articles cannot be got from one API, each article needs to be requested once, and these data are generally unchanged, so these data should be stored in the cache to avoid requesting repeatedly - -```js -const description = await ctx.cache.tryGet(link, async () => { - const result = await got.get(link); - - const $ = cheerio.load(result.data); - $('img').each(function (i, e) { - $(e).attr('src', $(e).attr('data-src')); - }); - - return $('.article-holder').html(); -}); -``` - -The implementation of tryGet can be seen [here](https://github.com/DIYgod/RSSHub/blob/master/lib/middleware/cache/index.js#L58). The 1st parameter is the cache key; the 2nd parameter is the cache data acquisition method (executed when cache miss); the 3rd parameter is the cache time, it should not be passed in normally and defaults to [CACHE_CONTENT_EXPIRE](/en/install/#cache-configurations); the 4th parameter determines whether to recalculate the expiration time ("renew" the cache) when the current attempt cache hits, `true` is on, `false` is off, default is on - ---- - -#### Produce RSS Feed - -Assign the acquired data to ctx.state.data, the middleware [template.js](https://github.com/DIYgod/RSSHub/blob/master/middleware/template.js) will then process the data and render the RSS output [/views/rss.art](https://github.com/DIYgod/RSSHub/blob/master/views/rss.art), the list of parameters: - -```js -ctx.state.data = { - title: '', // The feed title - link: '', // The feed link - description: '', // The feed description - language: '', // The language of the channel - allowEmpty: false, // default to false, set to true to allow empty item - item: [ - // An article of the feed - { - title: '', // The article title - author: '', // Author of the article - category: '', // Article category - // category: [''], // Multiple category - description: '', // The article summary or content - pubDate: '', // The article publishing datetime - guid: '', // The article unique identifier, optional, default to the article link below - link: '', // The article link - }, - ], -}; -``` - -::: warning Warning - -`title`, `subtitle` (only for atom), `author` (only for atom), `item.title`, and `item.author` should not contain linebreaks, consecutive white spaces, or start/end with white space(s). -Most RSS readers will automatically trim them, so they make no sense. However, some readers may not process them properly, so we will trim them before outputting to ensure these fields contain no linebreaks, consecutive white spaces, or start/end with white space(s). -If the route you are writing can not tolerate these trimmings, you should consider change the format of these fields. - -In addition, although other fields will not be forced trimmed, you should also try to avoid violations of the above rules. Especially when using Cheerio to extract web pages, you need to keep in mind that Cheerio will retain wraps and indentation. In particular, for `item.description`, any intended linebreaks should be converted to `
`, otherwise the RSS reader is likely to trim them; especially if you extract the RSS feed from JSON, the JSON returned by the source website is very likely to contain linebreaks that need to be displayed, so it must be converted in this case. - -::: - -##### Podcast feed - -Used for audio feed, these **additional** data are in accordance with many podcast players' subscription format: - -```js -ctx.state.data = { - itunes_author: '', // The channel's author, you must fill this data. - itunes_category: '', // Channel category - image: '', // Channel's image - item: [ - { - itunes_item_image: '', // The item image - itunes_duration: '', // The audio length in seconds (or H:mm:ss), optional - enclosure_url: '', // The item's audio link - enclosure_length: '', // The file size in Bytes, optional - enclosure_type: '', // Common types are: 'audio/mpeg' for .mp3, 'audio/x-m4a' for .m4a 'video/mp4' for .mp4 - }, - ], -}; -``` - -##### BT/Magnet feed - -Used for downloader feed, these **additional** data are in accordance with many downloaders' subscription format to trigger automated download: - -```js -ctx.state.data = { - item: [ - { - enclosure_url: '', // Magnet URI - enclosure_length: '', // The file size in Bytes, optional - enclosure_type: 'application/x-bittorrent', // Fixed to 'application/x-bittorrent' - }, - ], -}; -``` - -##### Media RSS - -These **additional** data are in accordance with many [Media RSS](http://www.rssboard.org/media-rss) softwares' subscription format: - -For example: - -```js -ctx.state.data = { - item: [ - { - media: { - content: { - url: post.file_url, - type: `image/${mime[post.file_ext]}`, - }, - thumbnail: { - url: post.preview_url, - }, - }, - }, - ], -}; -``` - -##### Interactions - -These **additional** data are in accordance with some softwares' subscription format: - -```js -ctx.state.data = { - item: [ - { - upvotes: 0, // default to undefined, how many upvotes for this article, - downvotes: 0, // default to undefined, how many downvotes for this article, - comments: 0, // default to undefined, how many comments for this article - }, - ], -}; -``` - ---- - -### Add the documentation - -1. Update the corresponding file in [documentation (/docs/en/)](https://github.com/DIYgod/RSSHub/blob/master/docs/en) directory, preview the docs via `npm run docs:dev` - - - Documentation uses vue component: - - `author`: route authors, separated by a single space - - `example`: route example - - `path`: route path - - `:paramsDesc`: route parameters description, in array, supports markdown - 1. parameter description must be in the order of its appearance in route path - 2. missing description will cause errors in `npm run docs:dev` - 3. `'` `"` must be escaped as `\'` `\"` - 4. route parameters ending with `?`, `*`, `+`, and a word represent `optional`, `zero or more`, `one or more`, and `mandatory` respectively. They are automatically determined by Vue component and do not need to be explicitly mentioned in the description - - Documentation examples: - - 1. No parameter: - - ```vue - - ``` - - Preview: - - * * * - - - - * * * - - 2. Multiple parameters: - - ```vue - - ``` - - Preview: - - * * * - - - - * * * - - 3. Use component slot for complicated description: - - ```vue - - - | 前端 | Android | iOS | 后端 | 设计 | 产品 | 工具资源 | 阅读 | 人工智能 | - | -------- | ------- | --- | ------- | ------ | ------- | -------- | ------- | -------- | - | frontend | android | ios | backend | design | product | freebie | article | ai | - - - ``` - - Preview: - - * * * - - - - | 前端 | Android | iOS | 后端 | 设计 | 产品 | 工具资源 | 阅读 | 人工智能 | - | -------- | ------- | --- | ------- | ------ | ------- | -------- | ------- | -------- | - | frontend | android | ios | backend | design | product | freebie | article | ai | - - - - * * * - -2. Please be sure to close the tag of ``! - -3. Execute `npm run format` to lint the code before you commit and open a pull request - -## Submit new RSSHub Radar rule - -### Debug - -Open browser's RSSHub Radar extension settings, switch to rules list, scroll down and you will see a text box, copy the new rules into the text box and start debugging - -### Code the rule - -Create `radar.js` under the corresponding [/lib/v2/](https://github.com/DIYgod/RSSHub/tree/master/lib/v2) route and the rules - -Simplified rules will be used for the following illustration: - -```js -{ - 'bilibili.com': { - _name: 'bilibili', - www: [{ - title: '分区视频', - docs: 'https://docs.rsshub.app/social-media.html#bilibili', - source: '/v/*tpath', - target: (params) => { - let tid; - switch (params.tpath) { - case 'douga/mad': - tid = '24'; - break; - default: - return false; - } - return `/bilibili/partion/${tid}`; - }, - }], - }, - 'twitter.com': { - _name: 'Twitter', - '.': [{ // for twitter.com - title: 'User timeline', - docs: 'https://docs.rsshub.app/en/social-media.html#twitter', - source: '/:id', - target: (params) => { - if (params.id !== 'home') { - return '/twitter/user/:id'; - } - }, - }], - }, - 'pixiv.net': { - _name: 'Pixiv', - 'www': [{ - title: 'User Bookmark', - docs: 'https://docs.rsshub.app/en/social-media.html#pixiv', - source: '/bookmark.php', - target: (params, url) => `/pixiv/user/bookmarks/${new URL(url).searchParams.get('id')}`, - }], - }, - 'weibo.com': { - _name: '微博', - '.': [{ - title: '博主', - docs: 'https://docs.rsshub.app/social-media.html#%E5%BE%AE%E5%8D%9A', - source: ['/u/:id', '/:id'], - target: (params, url, document) => { - const uid = document && document.documentElement.innerHTML.match(/\$CONFIG\['oid']='(\d+)'/)[1]; - return uid ? `/weibo/user/${uid}` : ''; - }, - }], - }, -} -``` - -The definition and usage of these fields are explained below: - -#### title - -Required, route name - -The corresponding name in RSSHub docs, e.g. the `title` for `Twitter User timeline` is `User timeline` - -#### docs - -Required, docs link - -e.g. the `docs` for `Twitter User timeline` is `https://docs.rsshub.app/en/social-media.html#twitter` - -Note that it is not `https://docs.rsshub.app/en/social-media.html#twitter-user-timeline`, hash should be positioned to the H1 heading - -#### source +[![Telegram Group](https://img.shields.io/badge/chat-telegram-brightgreen.svg?logo=telegram&style=for-the-badge)](https://t.me/rsshub) [![GitHub issues](https://img.shields.io/github/issues/DIYgod/RSSHub?color=bright-green&logo=github&style=for-the-badge)](https://github.com/DIYgod/RSSHub/issues) [![GitHub Discussions](https://img.shields.io/github/discussions/DIYgod/RSSHub?logo=github&style=for-the-badge)](https://github.com/DIYgod/RSSHub/discussions) -Optional, source path, leave it blank and it will never match, only appears in `RSSHub for current website` +## Before you begin -e.g. the `source` for `Twitter User timeline` is `/:id` +To create an RSS feed, you'll need to use a combination of Git, HTML, JavaScript, jQuery, and Node.js. -Let's say we are in `https://twitter.com/DIYgod`, which matches`twitter.com/:id`, the resulting params is `{id: 'DIYgod'}`, the extension will then generate an RSSHub subscription address based on the params `target` +If you don't know much about them but would like to learn them, here are some good resources: -Please note that `source` can only match URL Path, if the parameters are in URL Param and URL Hash please use `target` +- [JavaScript Tutorials on MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript#tutorials) +- [W3Schools](https://www.w3schools.com/) +- [Git course on Codecademy](https://www.codecademy.com/learn/learn-git) -#### target +If you'd like to see examples of how other developers use these technologies to create RSS feeds, you can take a look at some of the code in [our repository](https://github.com/DIYgod/RSSHub/tree/master/lib/v2). -Optional, RSSHub path, leave it blank to not generate an RSSHub path +## Submit new RSSHub rules -The corresponding path in RSSHub docs, e.g. the `target` for `Twitter User timeline` is `/twitter/user/:id` +If you've found a website that doesn't offer an RSS feed, you can create an RSS rule for it using RSSHub. An RSS rule is a short Node.js program code (hereafter referred to as "route") that tells RSSHub how to extract content from a website and generate an RSS feed. By creating a new RSS route, you can help make content from your favourite websites more accessible and easier to follow. -The source path in the previous step matches `id` with `DIYgod`, the `:id` in RSSHub path will be replaced with `DIYgod`, resulting in `/twitter/user/DIYgod`, which is the result we want +Before you start writing an RSS route, please make sure that the source site does not provide RSS. Some web pages will include a link element with type `application/atom+xml` or `application/rss+xml` in the HTML header to indicate the RSS link. -Furthermore, if the source path does not match the desired parameters, we can use `target` as a function with `params`, `url` and `document` parameters +Here's an example of what an RSS link might look like in the HTML header: ``. If you see a link like this, it means that the website already has an RSS feed and you don't need to create a new RSS route for it. -`params` are the parameters matched by `source` in the previous step, `url` is the page url, `document` is the page document +### Getting started -Note that the `target` method runs in a sandbox, any changes to `document` will not be reflected in the web page +In this guide, you'll learn how to create a new RSS route from scratch. We'll cover everything from setting up your development environment to submitting your code to the RSSHub repository. By the end of this guide, you'll be able to create your own RSS feeds for websites that don't offer them. -### RSSBud +[Ready to get started? Click here to dive into the guide!](/en/joinus/new-rss/prerequisites.html) -[RSSBud](https://github.com/Cay-Zhang/RSSBud) supports RSSHub Radar rules and will also be updated automatically, but please note that: +## Submit new RSSHub Radar rules -- Using `'.'` subdomain allows RSSBud to support common mobile domains such as `m` / `mobile` +### Before you start -- Using `document` in `target` does not apply to RSSBud: RSSBud is not a browser extension, it only fetches and analyzes the URL of a website +It's recommended that you download and install RSSHub Radar in your browser before you start. -### Additional Information +Once you have installed RSSHub Radar, open the settings and switch to the "List of rules" tab. Then scroll down to the bottom of the page and you will see a text field. Here, you can replace the old rules with your new rules for debugging. -Adding `radar="1"` in the RSSHub docs will show a `support browser extension` badge +[Let's start!](/en/joinus/new-radar.html) -If RSSBud is also supported, adding `rssbud="1"` will show a `support rssbud` badge +Get RSSHub Radar for Chromium +Get Get RSSHub Radar for Firefox +Get RSSHub Radar for Edge +Get RSSHub Radar for Safari diff --git a/docs/en/joinus/script-standard.md b/docs/en/joinus/script-standard.md index 45994b749e24d4..a026f1847853b6 100644 --- a/docs/en/joinus/script-standard.md +++ b/docs/en/joinus/script-standard.md @@ -1,19 +1,76 @@ +--- +sidebarDepth: 2 +--- # Script Standard -::: warning Warning +## Code Style -This standard is still WIP and may change over time, so please remember to check it often! +### General Guidelines -::: +- **Be consistent!** +- Avoid using deprecated features. +- Avoid modifying `yarn.lock` and `package.json`, unless you are adding a new dependency. +- Conbine repetitive code into functions. +- Prefer higher ECMAScript Standard features over lower ones. +- Sort the entries alphabetically (uppercase first) to make it easier to find an entry. +- Use HTTPS instead of HTTP whenever possible. +- Use WebP format instead of JPG whenever possible since it offers better compression. + +### Formatting + +#### Indentation + +- Use 4 spaces for indentation for consistent and easy-to-read code. + +#### Semicolons + +- Add a semicolon at the end of each statement for improved readability and consistency. + +#### String + +- Use single quotes instead of double quotes whenever possible for consistency and readability. +- Use [template literals](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Template_literals) over complex string concatenation. +- Use [template literals](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Template_literals) for GraphQL queries as they make the code more concise and easy to read. + +#### Whitespace + +- Add an empty line at the end of each file. +- Avoid trailing whitespace for a clean and readable codebase. + +### Language Features + +#### Casting + +- Avoid re-casting the same type. + +#### Functions + +- Prefer [arrow functions](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions) over the `function` keyword. + +#### Loops + +- Use `for-of` instead of `for` for arrays ([javascript:S4138](https://rules.sonarsource.com/javascript/RSPEC-4138)). + +#### Variables + +- Use `const` and `let` instead of `var`. +- Declare one variable per declaration. + +### Naming + +- Use `lowerCamelCase` for variables and functions to adhere to standard naming conventions. +- Use `kebab-case` for files and folders. `snake_case` is also acceptable. +- Use `CONSTANT_CASE` for constants. -When writing a new route, RSSHub will read the following in a folder : +## v2 Route Standard -- `router.js` Register route -- `maintainer.js` Fetch route path and maintainer -- `radar.js` Fetch the website and the matching rules corresponding to the route: -- `templates` Rendering templates +When creating a new route in RSSHub, you need to organize your files in a specific way. Your namespace folder should be stored in the `lib/v2` directory and should include three mandatory files: -**The above files are mandatory for all routes** +- `router.js` Registers the routes +- `maintainer.js` Provides information about the route maintainer +- `radar.js` Provide a [RSSHub Radar](https://github.com/DIYgod/RSSHub-Radar) rule for each route + +Your namespace folder structure should look like this: ``` ├───lib/v2 @@ -25,123 +82,62 @@ When writing a new route, RSSHub will read the following in a folder : │ ├─── radar.js │ ├─── someOtherJs.js │ └───test -│ └───someOtherScript +│ └───someOtherNamespaces ... ``` -**All eligible routes under `/v2` path will be loaded automatically without the need of updating `router.js`** - -## Route Example - -Refer to `furstar`: `./lib/v2/furstar` - -You can copy the folder as a start-up route template - -## Register Route +**All eligible routes under the `lib/v2` path will be automatically loaded without the need for updating the `lib/router.js`.** -`router.js` should export a method that provides a `@koa/router` object when we initialize the route +### Namespace -### Naming Standard - -RSSHub will append all the route folder names in front of the actual route by default. The route maintainers can think that the one he gets is the root. We will append the corresponding namespace under which the developers have all the control - -### Example - -```js -module.exports = function (router) { - router.get('/characters/:lang?', require('./index')); - router.get('/artists/:lang?', require('./artists')); - router.get('/archive/:lang?', require('./archive')); -}; -``` +RSSHub appends the name of all route namespace folders in front of the actual route. Route maintainers should think of the namespace as the root. -## Maintainer List +#### Naming Standard -`maintainer.js` should export an object from which we will retrieve the maintainer information when we get the path-related information, etc. +- Use the second-level domain (SLD) as your namespace. You can find more information about URL structure [here](/en/joinus/new-radar.html#top-level-object-key). +- Do not create variations of the same namespace. For more information, see [this page](/en/joinus/new-rss/before-start.html#create-a-namespace) -- key: `@koa/router` Corresponding path -- value: Array, includes all maintainers' Github Username +### Registering a Route -Github ID may be a better choice, but it is inconvenient to deal with the follow-up, so use GitHub Username for now. +To register a route, the `router.js` file should export a method that provides a `@koa/router` object when initializing the route. -### Example +### Maintainer List -```js -module.exports = { - '/characters/:lang?': ['NeverBehave'], - '/artists/:lang?': ['NeverBehave'], - '/archive/:lang?': ['NeverBehave'], -}; -``` +The `maintainer.js` file should export an object that provides maintainer information related to the route, including: -`npm run build:maintainer` will generate a list of maintainers under `assets/build` +- Key: Corresponding path in the `@koa/router` object +- Value: Array of string, including all maintainers' GitHub ID. -## Radar Rules +To generate a list of maintainers, use the following command: `yarn build:maintainer`, which will create the list under `assets/build/`. -Writing style: +::: danger Warning +The path in the `@koa/router` object should be the same as the `path` in the corresponding documentation before the namespace appended in front of it. +::: -**We currently require all routes to include this file and include the corresponding domain name -- we don't require an exact route match. The minimum requirement is that it will show up on the corresponding site. This file will also be used later to help with bug feedback.** +### Radar Rules -### Example +All routes are required to include the `radar.js` file, which includes the corresponding domain name. The minimum requirement for a successful match is for the rule to show up on the corresponding site which requires filling in the `title` and `docs` fields. -```js -module.exports = { - 'furstar.jp': { - _name: 'Furstar', - '.': [ - { - title: '最新售卖角色列表', - docs: 'https://docs.rsshub.app/shopping.html#furstar-zui-xin-shou-mai-jiao-se-lie-biao', - source: ['/:lang', '/'], - target: '/furstar/characters/:lang', - }, - { - title: '已经出售的角色列表', - docs: 'https://docs.rsshub.app/shopping.html#furstar-yi-jing-chu-shou-de-jiao-se-lie-biao', - source: ['/:lang/archive.php', '/archive.php'], - target: '/furstar/archive/:lang', - }, - { - title: '画师列表', - docs: 'https://docs.rsshub.app/shopping.html#furstar-hua-shi-lie-biao', - source: ['/'], - target: '/furstar/artists', - }, - ], - }, -}; -``` +To generate a complete `radar-rules.js` file, use the following command: `yarn build:radar`, which will create the file under `assets/build/`. -`npm run build:radar` will generate a complete `radar-rules.js` under `/assets/build/` +::: tip Tips +Remember to remove all build artifacts in `assets/build/` before committing. +::: -## Template +### Rendering Templates -We currently require all routes, when rendering content with HTML such as `description`, **must** use the art engine for layout +When rendering custom content with HTML, such as `item.description`, using [art-template](https://aui.github.io/art-template/) for layout is mandatory. -art documentation: +All templates should be placed in the namespace's `templates` folder with the `.art` file extension. -Apart from that, all templates should be placed in the script's `templates` folder: We will use this to provide custom template switching/rendering, etc. +#### Example -### Example +Here's an example taken from the [furstar](https://github.com/DIYgod/RSSHub/blob/master/lib/v2/furstar) namespace: -```art -
- - {{ if link !== null }} - {{name}} - {{ else }} - {{name}} - {{ /if }} -
-``` +<<< @/lib/v2/furstar/templates/author.art ```js +const path = require('path'); const { art } = require('@/utils/render'); const renderAuthor = (author) => art(path.join(__dirname, 'templates/author.art'), author); ``` - -## ctx.state.json - -The script can also provide a custom object for debugging: Access the corresponding route + `.debug.json` to get the corresponding content - -We don't have any restrictions on the formatting of this part yet, it's completely optional, and we will see where this option goes diff --git a/docs/en/joinus/use-cache.md b/docs/en/joinus/use-cache.md index 7c3c8d91e50b38..d3efdcde628b7c 100644 --- a/docs/en/joinus/use-cache.md +++ b/docs/en/joinus/use-cache.md @@ -1,5 +1,84 @@ -# Use Cache +# Using Cache -Some routes require visiting several pages when generating RSS feeds, and these pages are not likely to be changed very often. In this case, caching should be used for reducing the server load and saving unnecessary traffics/calculations. Here are some scenarios and details about the use of the caching tools. +All routes have a cache that expires after a short duration. You can change how long the cache lasts by modifying the `CACHE_EXPIRE` value in the `lib/config.js` file using environment variables. However, for interfaces that have less frequently updated content, it's better to specify a longer cache expiration time using `CACHE_CONTENT_EXPIRE` instead. - +For example, to retrieve the full text of the first comment for each issue, you can make a request to `${baseUrl}/${user}/${repo}/issues/${id}`, since this data is unavailable through `${baseUrl}/${user}/${repo}/issues`. It's recommended to store this data in the cache to avoid making repeated requests to the server. + +Here's an example of how you can use the cache to retrieve the data: + +```js + const items = await Promise.all( + list.map((item) => + ctx.cache.tryGet(item.link, async () => { + const { data: response } = await got(item.link); + const $ = cheerio.load(response); + + item.description = $('.comment-body').first().html(); + + return item; + }) + ) + ); +``` + +The above code snippet from [Create Your Own RSSHub Route](/en/joinus/new-rss/start-code.html#via-html-web-page-using-got-better-reading-experience) shows how to use the cache to get the full text of the first comment of each issue. `ctx.cache.tryGet()` is used to determine if the data is already available within the cache. If it's not, the code retrieves the data and stores it in the cache. + +The object returned from the previous statement will be reused, and an extra `description` property will be added to it. The returned cache for each `item.link` will be `{ title, link, pubDate, author, category, description }`. The next time the same path is requested, this processed cache will be used instead of making a request to the server and recomputing the data. + +::: warning Warning +Any assignments to variables that are declared outside of the `tryGet()` function will not be processed under a cache-hit scenario. For example, the following code will not work as expected: + +```js + let variable = 'value'; + await ctx.cache.tryGet('cache:key', async () => { + variable = 'new value'; + const newVariable = 'new variable'; + return newVariable; + }) + console.log(variable); // cache miss: 'new value', cache hit: 'value' + +``` + +::: + +## API + +### ctx.cache.tryGet(key, getValueFunc [, maxAge [, refresh ]]) + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| key | `string` | *(Required)* The key used to store and retrieve the cache. You can use `:` as a separator to create a hierarchy. | +| getValueFunc | `function` \| `string` | *(Required)* A function that returns data to be cached when a cache miss occurs. +| maxAge | `number` | *(Optional)* The maximum age of the cache in seconds. If not specified, `CACHE_CONTENT_EXPIRE` will be used. | +| refresh | `boolean` | *(Optional)* Whether to renew the cache expiration time when the cache is hit. `true` by default. | + +#### Defined in + +[lib/middleware/cache/index.js](https://github.com/DIYgod/RSSHub/blob/master/lib/middleware/cache/index.js#L58) + +::: tip Tips +Below are advanced methods for using cache. You should use `ctx.cache.tryGet()` most of the time. + +Note that you need to use `JSON.parse()` when retrieving the cache using `ctx.cache.get()`. +::: + +### ctx.cache.get(key [, refresh ]) + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| key | `string` | *(Required)* The key used to retrieve the cache. You can use `:` as a separator to create a hierarchy. | +| refresh | `boolean` | *(Optional)* Whether to renew the cache expiration time when the cache is hit. `true` by default. | + +### ctx.cache.set(key, value [, maxAge ]) + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| key | `string` | *(Required)* The key used to store the cache. You can use `:` as a separator to create a hierarchy. | +| value | `function`\| `string` | *(Required)* The value to be cached. | +| maxAge | `number` | *(Optional)* The maximum age of the cache in seconds. If not specified, `CACHE_CONTENT_EXPIRE` will be used. | diff --git a/docs/en/journal.md b/docs/en/journal.md index ef1fa82a584654..7b7c86129b08a6 100644 --- a/docs/en/journal.md +++ b/docs/en/journal.md @@ -166,9 +166,9 @@ Google Scholar has strict anti-crawling mechanism implemented, the demo below do ::: -1. Basic mode, sample query is the keywords desired, eg.「data visualization」, [https://rsshub.app/google/scholar/data+visualization](https://rsshub.app/google/scholar/data+visualization). +1. Basic mode, sample query is the keywords desired, eg.「data visualization」, [https://rsshub.app/google/scholar/data+visualization](https://rsshub.app/google/scholar/data+visualization). -2. Advanced mode, visit [Google Scholar](https://scholar.google.com/schhp?hl=en&as_sdt=0,5), click the top left corner and select「Advanced Search」, fill in your conditions and submit the search. The URL should look like this: [https://scholar.google.com/scholar?as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=en&as_sdt=0%2C5](https://scholar.google.com/scholar?as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=en&as_sdt=0%2C5), copy everything after `https://scholar.google.com/scholar?` from the URL and use it as the query for this route. The complete URL for the above example should look like this: [https://rsshub.app/google/scholar/as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=en&as_sdt=0%2C5](https://rsshub.app/google/scholar/as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=en&as_sdt=0%2C5). +2. Advanced mode, visit [Google Scholar](https://scholar.google.com/schhp?hl=en&as_sdt=0,5), click the top left corner and select「Advanced Search」, fill in your conditions and submit the search. The URL should look like this: [https://scholar.google.com/scholar?as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=en&as_sdt=0%2C5](https://scholar.google.com/scholar?as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=en&as_sdt=0%2C5), copy everything after `https://scholar.google.com/scholar?` from the URL and use it as the query for this route. The complete URL for the above example should look like this: [https://rsshub.app/google/scholar/as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=en&as_sdt=0%2C5](https://rsshub.app/google/scholar/as_q=data+visualization&as_epq=&as_oq=&as_eq=&as_occt=any&as_sauthors=&as_publication=&as_ylo=2018&as_yhi=&hl=en&as_sdt=0%2C5).
@@ -272,10 +272,10 @@ You can get all short name of a journal from | nmat | Nature Materials | [/nature/research/nmat](https://rsshub.app/nature/research/nmat) | | natmachintell | Nature Machine Intelligence | [/nature/research/natmachintell](https://rsshub.app/nature/research/natmachintell) | -- Using router (`/nature/research/` + "short name for a journal") to query latest research paper for a certain journal of Nature Publishing Group. +- Using router (`/nature/research/` + "short name for a journal") to query latest research paper for a certain journal of Nature Publishing Group. If the `:journal` parameter is blank, then latest research of Nature will return. -- The journals from NPG are run by different group of people, and the website of may not be consitent for all the journals -- Only abstract is rendered in some researches +- The journals from NPG are run by different group of people, and the website of may not be consitent for all the journals +- Only abstract is rendered in some researches
@@ -294,8 +294,8 @@ You can get all short name of a journal from | nmat | Nature Materials | [/nature/news-and-comment/nmat](https://rsshub.app/nature/news-and-comment/nmat) | | natmachintell | Nature Machine Intelligence | [/nature/news-and-https://rsshub.app/comment/natmachintell](https://rsshub.app/nature/news-and-comment/natmachintell) | -- Using router (`/nature/research/` + "short name for a journal") to query latest research paper for a certain journal of Nature Publishing Group. -- The journals from NPG are run by different group of people, and the website of may not be consitent for all the journals +- Using router (`/nature/research/` + "short name for a journal") to query latest research paper for a certain journal of Nature Publishing Group. +- The journals from NPG are run by different group of people, and the website of may not be consitent for all the journals
@@ -388,8 +388,8 @@ In diff --git a/docs/en/multimedia.md b/docs/en/multimedia.md index 9f5ed7f4e44adc..55a47c97794535 100644 --- a/docs/en/multimedia.md +++ b/docs/en/multimedia.md @@ -461,9 +461,9 @@ See [Directory](https://www.javlibrary.com/en/star_list.php) to view all stars. Refer to [Pornhub F.A.Qs](https://help.pornhub.com/hc/en-us/articles/360044327034-How-do-I-change-the-language-), English by default. For example: -- `cn` (Chinese), for Pornhub in China ; +- `cn` (Chinese), for Pornhub in China ; -- `jp` (Japanese), for Pornhub in Japan etc. +- `jp` (Japanese), for Pornhub in Japan etc. ## PRESTIGE(プレステージ) diff --git a/docs/en/new-media.md b/docs/en/new-media.md index ff83c90ed214dc..b168c45cc75670 100644 --- a/docs/en/new-media.md +++ b/docs/en/new-media.md @@ -53,7 +53,7 @@ Compared to the official one, the RSS feed generated by RSSHub not only has more
-## ASML Holding N.V. +## ASML Holding N.V ### Press releases & announcements @@ -582,8 +582,8 @@ This route provides a flexible plan with full text content to subscribe specific Compared to the official one, this feed: -1. supports LaTeX formulas, and -2. displays all pictures in the article (except those print-hidden multimedia materials). +1. supports LaTeX formulas, and +2. displays all pictures in the article (except those print-hidden multimedia materials).
@@ -718,13 +718,13 @@ Compared to the official one, this feed: -- Available languages: +- Available languages: | Simplified Chinese | Traditional Chinese | English | | ------------------ | ------------------- | ------- | | zh | zh_tw | en | -- Available topics by locale: +- Available topics by locale: | Languages | | | | | | | ------------------- | -------- | ----- | -------- | -------- | -------- | @@ -732,9 +732,9 @@ Compared to the official one, this feed: | Traditional Chinese | 最新文章 | 科普 | 測評報告 | 發燒入門 | 攝影入門 | 古典音樂入門 | | English | Phone | Audio | Album | Review | -- Soomal offers official RSS subscriptions - - Soomal website:[http://www.soomal.com/doc/101.rss.xml](http://www.soomal.com/doc/101.rss.xml) - - Soomal forum and comments:[http://www.soomal.com/bbs/101.rss.xml](http://www.soomal.com/bbs/101.rss.xml) +- Soomal offers official RSS subscriptions + - Soomal website:[http://www.soomal.com/doc/101.rss.xml](http://www.soomal.com/doc/101.rss.xml) + - Soomal forum and comments:[http://www.soomal.com/bbs/101.rss.xml](http://www.soomal.com/bbs/101.rss.xml) diff --git a/docs/en/programming.md b/docs/en/programming.md index 9ebe1e7199b97a..00556f7820142d 100644 --- a/docs/en/programming.md +++ b/docs/en/programming.md @@ -183,9 +183,10 @@ For instance, the `/github/topics/framework/l=php&o=desc&s=stars` route will gen > - If there are special characters such as `/` in the **branch name**, they need to be encoded with urlencode, usually `/` needs to be replaced with `%2f` > - If there are special characters in the **file path**, you need to use urlencode to encode them, but the file path can be recognized normally `/` characters > - If the **file path** ends with `.rss`, `.atom`, `.json`, you need to replace the `.` in the suffix with `%2e` -> > Reeder will make an error when subscribing to `% 2erss` or similar suffixes. At this time, add`.rss` after the route to subscribe -> > -> > Such as: replace `https://rsshub.app/github/file/DIYgod/RSSHub/master/lib/router%2ejs` to `https://rsshub.app/github/file/DIYgod/RSSHub/master/lib/router%2ejs.rss` +> +> > Reeder will make an error when subscribing to `% 2erss` or similar suffixes. At this time, add`.rss` after the route to subscribe +> > +> > Such as: replace `https://rsshub.app/github/file/DIYgod/RSSHub/master/lib/router%2ejs` to `https://rsshub.app/github/file/DIYgod/RSSHub/master/lib/router%2ejs.rss`
diff --git a/docs/en/support/README.md b/docs/en/support/README.md index 8cb7d9148f3a28..b2afdfa7b3cbe1 100644 --- a/docs/en/support/README.md +++ b/docs/en/support/README.md @@ -4,7 +4,7 @@ sidebar: auto # Support RSSHub -RSSHub is open source and completely free under the MIT license. However, just like any other open source project, as the project grows, the hosting, development and maintenance requires funding support. +RSSHub is open source and completely free under the MIT license. However, just like any other open source projects, as the project grows, the hosting, development and maintenance require funding support. You can support RSSHub via donations. @@ -13,13 +13,14 @@ You can support RSSHub via donations. Recurring donors will be rewarded via express issue response, or even have your name displayed on our GitHub page and website. - Become a Sponser on [GitHub](https://github.com/sponsors/DIYgod) +- Become a Sponser on [Open Collective](https://opencollective.com/rsshub) - Become a Sponser on [Patreon](https://www.patreon.com/DIYgod) - Become a Sponser on [爱发电](https://afdian.net/@diygod) - Contact us directly: i@diygod.me ## One-time Donation -We accept donations via the following ways: +We accept donations in the following ways: - [WeChat Pay](https://diygod.me/images/wx.jpg) - [Alipay](https://diygod.me/images/zfb.jpg) diff --git a/docs/en/usage.md b/docs/en/usage.md index 4268cf0140537c..bdb03f3c1014f4 100644 --- a/docs/en/usage.md +++ b/docs/en/usage.md @@ -1,12 +1,12 @@ # Getting Started -## Generate a RSS Feed +## Generate an RSS Feed To subscribe to a Twitter user's timeline, first look at the route document of [Twitter User Timeline](en/social-media.html#twitter-user-timeline). `/twitter/user/:id` is the route where `:id` is the actual Twitter username you need to replace. For instance, `/twitter/user/DIYgod` with a prefix domain name will give you the timeline of Twitter user DIYgod. -The demo instance will generate a RSS feed at , use your own domain name when applicable. This feed should work with all RSS readers conforming to the RSS Standard. +The demo instance will generate an RSS feed at , use your own domain name when applicable. This feed should work with all RSS readers conforming to the RSS Standard. You can replace the domain name `https://rsshub.app` with your [self-hosted instance](/en/install/). diff --git a/docs/joinus/advanced-feed.md b/docs/joinus/advanced-feed.md new file mode 100644 index 00000000000000..19cb5b3e846059 --- /dev/null +++ b/docs/joinus/advanced-feed.md @@ -0,0 +1,198 @@ +# 制作多媒体 RSS 订阅源 + +本指南面向希望深入了解如何制作 RSS 订阅源的高级用户。如果您是第一次制作 RSS 订阅源,我们建议先阅读 [制作自己的 RSSHub 路由](/joinus/new-rss/start-code.html)。 + +一旦您获取了要包含在您的 RSS 订阅源中的数据,就可以将其传递给 `ctx.state.data`。然后RSSHub的中间件 [`template.js`](https://github.com/DIYgod/RSSHub/blob/master/lib/middleware/template.js) 将处理数据并以所需的格式呈现 RSS 输出(默认为RSS 2.0)。除了 [制作自己的 RSSHub 路由](/joinus/new-rss/start-code.html) 中提到的字段外,您还可以使用以下字段进一步自定义 RSS 订阅源。 + +需要注意的是,并非所有字段都适用于所有的输出格式,因为 RSSHub 支持多种输出格式。下表显示了不同输出格式兼容的字段。我们使用以下符号表示兼容性:`A` 表示 Atom,`J` 表示 JSON Feed,`R` 表示 RSS 2.0。 + +频道级别 + +以下表格列出了你可以用来定制你的 RSS 订阅源频道级别的字段: + +| 字段 | 描述 | 默认值 | 兼容性 | +| :---------- | :---------- | :----------- | :------------ | +| **`title`** | *(推荐)* 源的名称,应为纯文本 | `RSSHub` | A, J, R | +| **`link`** | *(推荐)* 与源关联的网站网址,应链接到一个可读的网站 | `https://rsshub.app` | A, J, R | +| **`description`** | *(可选)* 源的摘要,应为纯文本 | 如果未指定,默认为 **`title`** | J, R | +| **`language`** | *(可选)* 源的主要语言,应为 [RSS语言代码](https://www.rssboard.org/rss-language-codes) 或 ISO 639 语言代码之一 | `zh-cn` | J, R | +| **`image`** | *(推荐)* 表示频道的高清图片的网址 | `undefinded` | J, R | +| **`icon`** | *(可选)* Atom 源的图标 | `undefinded` | J | +| **`logo`** | *(可选)* RSS 源的标志 | `undefinded` | J | +| **`subtitle`** | *(可选)* Atom 源的副标题 | `undefinded` | A | +| **`author`** | *(可选)* Atom 源的作者或 JSON Feed 的作者 | `RSSHub` | A, J | +| **`itunes_author`** | *(可选)* 播客源的作者 | `undefinded` | R | +| **`itunes_category`** | *(可选)* 播客源的分类 | `undefinded` | R | +| **`itunes_explicit`** | *(可选)* 播客源的 explicit | `undefinded` | R | +| **`allowEmpty`** | *(可选)* 是否允许空源。如果设置为 `true`,即使没有文章,也会生成源 | `undefinded` | A, J, R | + +在 RSS 订阅源中,每个条目都由描述它的一组字段表示。下表列出了可用的字段: + +| 字段 | 描述 | 默认值 | 兼容性 | +| :---------- | :---------- | :------ | :------ | +| **`title`** | *(必填)* 条目的标题,应仅使用纯文本 | `undefinded` | A, J, R | +| **`link`** | *(推荐)* 条目的链接,应链接到可读的网站 | `undefinded` | A, J, R | +| **`description`** | *(推荐)* 条目的内容。对于 Atom 订阅,应是 `atom:content` 元素。对于 JSON Feed,应是 `content_html` 字段 | `undefinded` | A, J, R | +| **`author`** | *(可选)* 条目的作者 | `undefinded` | A, J, R | +| **`category`** | *(可选)* 条目的分类。字符串或字符串数组皆可 | `undefinded` | A, J, R | +| **`guid`** | *(可选)* 条目的唯一标识符。如果未指定,默认为 **`link || title`** | `undefinded` | A, J, R | +| **`pubDate`** | *(推荐)* 条目的发布日期,应该 [遵从规范](/joinus/pub-date.html) 是 [Date object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date) | `undefinded` | A, J, R | +| **`updated`** | *(可选)* 条目的最后修改日期,应该是 [Date object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date) | `undefinded` | A, J | +| **`itunes_item_image`** | *(可选)* 条目相关联的图片的网址 | `undefinded` | R | +| **`itunes_duration`** | *(可选)* 音频或视频条目的长度,以秒为单位(或格式为 H:mm:ss),应为数字或字符串 | `undefinded` | J, R | +| **`enclosure_url`** | *(可选)* 条目相关联的附件的网址 | `undefinded` | J, R | +| **`enclosure_length`** | *(可选)* 附件文件的大小(以 **byte** 为单位),应为数字 | `undefinded` | J, R | +| **`enclosure_type`** | *(可选)* 附件文件的 MIME 类型,应为字符串 | `undefinded` | J, R | +| **`upvotes`** | *(可选)* 条目的赞数,应为数字 | `undefinded` | A | +| **`downvotes`** | *(可选)* 条目的踩数,应为数字 | `undefinded` | A | +| **`comments`** | *(可选)* 条目的评论数,应为数字 | `undefinded` | A | +| **`media.*`** | *(可选)* 条目相关的媒体。更多详情请参见 [媒体 RSS](https://www.rssboard.org/media-rss) | `undefinded` | R | +| **`doi`** | *(可选)* 条目的数字对象标识符 (DOI),应为格式为 `10.xxx/xxxxx.xxxx` 的字符串 | `undefinded` | R | + +::: warning 格式考虑 +在指定 RSS 订阅源中的某些字段时,重要的是要注意一些格式考虑因素。具体来说,您应避免在以下字段中包含任何换行符、连续的空格或前导/尾随空格:**`title`**,**`subtitle`**(仅适用于 Atom),**`author`**(仅适用于 Atom),**`item.title`** 和 **`item.author`**。 + +虽然大多数 RSS 阅读器将自动修剪这些空字符,但有些阅读器可能无法正确处理它们。因此,为确保与所有 RSS 阅读器兼容,我们建议在输出这些字段之前将其修剪。如果您制作的路由无法容忍修剪这些空字符,您应考虑更改它们的格式。 + +另外,虽然其他字段不会被强制修剪,但我们建议尽可能避免违反上述格式规则。如果您正在使用 Cheerio 从网页中提取内容,时刻谨记 Cheerio 会保留换行和缩进。特别是对于 **`item.description`** 字段,任何预期之内的换行都应转换为 `
` 标签,以防止其被 RSS 阅读器修剪。尤其是您从 JSON 数据中制作 RSS 订阅时,目标网站返回的 JSON 很有可能含有需要显示的换行符,在这种情况下,应将它们转换为 `
` 标签。 + +请牢记这些格式考虑因素,以确保您的 RSS 订阅源与所有 RSS 阅读器兼容。 +::: + +## 制作 BitTorrent/磁力订阅源 + +RSSHub 支持制作 BitTorrent/磁力订阅源,这将帮助你的 RSS 订阅源。要制作 BitTorrent/磁力订阅源,您需要在 RSS 源添加附加字段,以符合 BitTorrent 客户端的订阅格式。 + +以下是制作 BitTorrent/磁力订阅源的示例: + +```js +ctx.state.data = { + item: [ + { + enclosure_url: '', // 磁力链接 + enclosure_length: '', // 文件大小(以 bytes 为单位)(可选) + enclosure_type: 'application/x-bittorrent', // 应固定为 'application/x-bittorrent' + }, + ], +}; +``` + +在 RSS 源中包含这些字段,您将能够制作被 BitTorrent 客户端识别并自动下载的 BitTorrent/磁力订阅源。 + +### 更新文档 + +如果您要在 RSSHub 路由中添加对 BitTorrent/磁力订阅支持,最重要的是在文档以反映此功能。要做到这一点,您需要将 `Route` 组件的 `supportBT` 属性设置为 `"1"`。 以下是一个示例: + +```vue + +``` + +通过将 `supportBT` 属性设置为 `"1"`,您将能够准确反映您的路由支持 BitTorrent/磁力订阅。 + +## 制作期刊订阅源 + +RSSHub支持制作期刊订阅源。如果用户提供 [通用参数](/parameter.html#shu-chu-sci-hub-lian-jie) `scihub`,则可以将 `item.link` 替换为 Sci-hub 链接。要制作期刊订阅源,您需要在您的 RSS 源中包含一个附加字段: + +```js +ctx.state.data = { + item: [ + { + doi: '', // 条目的 DOI(例如,'10.47366/sabia.v5n1a3') + }, + ], +}; +``` + +通过在 RSS 源中包含 `doi` 字段,您将能够制作与 RSSHub 的 Sci-hub 功能兼容的期刊订阅源。 + +### 更新文档 + +要显示您制作的期刊订阅源支持 Sui-hub 功能,您需要将 `Route` 组件的 `supportScihub` 属性设置为 `"1"`。以下是一个示例: + +```vue + +``` + +通过将 `supportSciHub` 属性设置为 `"1"`,路由文档将准确反映其支持提供具有 Sci-hub 链接的期刊订阅源。 + +## 制作播客订阅源 + +RSSHub 支持制作与播客播放器订阅格式兼容的播客订阅源。要制作播客订阅源,您需要在RSS源中包含几个附加字段: + +```js +ctx.state.data = { + itunes_author: '', // **必需**,应为主播名称 + itunes_category: '', // 播客分类 + image: '', // 专辑封面,作为播客源时**必填** + item: [ + { + itunes_item_image: '', // 条目的封面图像 + itunes_duration: '', // 可选,音频的长度,以秒为单位 或 H:mm:ss 格式 + enclosure_url: '', // 音频直链 + enclosure_length: '', // 可选,文件大小,以 Byte 为单位 + enclosure_type: '', // 音频文件 MIME 类型(常见类型 .mp3 是 'audio/mpeg',.m4a 是 'audio/x-m4a',.mp4 是 'video/mp4') + }, + ], +}; +``` + +通过在 RSS 源中包含这些字段,您将能够制作与播客播放器兼容的播客订阅源。 + +### 更新文档 + +要显示您制作的订阅源与播客播放器兼容,您需要将 `Route` 组件的 `supportPodcast` 属性设置为 `"1"`。以下是一个示例: + +```vue + +``` + +通过将 `supportPodcast` 属性设置为 `"1"`,路由文档将准确反映其支持播客订阅。 + +## 制作媒体订阅源 + +RSSHub支持制作与 [Media RSS](https://www.rssboard.org/media-rss) 格式兼容的媒体订阅源。要制作体订阅源订阅源,您需要在 RSS 源中包含这些附加字段。 + +以下是制作媒体订阅源的示例: + +```js +ctx.state.data = { + item: [ + { + media: { + content: { + url: '...', // 媒体内容的 URL + type: '...', // 媒体内容的 MIME 类型(例如,对于 .mp3 文件是 'audio/mpeg') + }, + thumbnail: { + url: '...', // 缩略图 URL + }, + '...': { + '...': '...', // 亦可包含其他媒体属性 + } + }, + }, + ], +}; +``` + +通过在 RSS 源中包含这些字段,您将能够制作与 [Media RSS](https://www.rssboard.org/media-rss) 格式兼容的媒体订阅源。 + +## 制作包含互动的 Atom 订阅源 + +RSSHub支持制作包含互动,如点赞、反对和评论的 Atom 订阅源。要制作带有互动的 Atom 订阅源,您需要在 RSS 源中包含附加字段,用于指定每个条目的互动计数。 + +以下是制作带有互动的 Atom 订阅源的示例: + +```js +ctx.state.data = { + item: [ + { + upvotes: 0, // 条目的点赞数 + downvotes: 0, // 条目的踩数 + comments: 0, // 条目的评论数 + }, + ], +}; +``` + +通过在 Atom 源中包含这些字段,您将能够制作包含互动的 Atom 订阅源,这些源与支持 Atom 订阅源的阅读器兼容。 diff --git a/docs/joinus/debug.md b/docs/joinus/debug.md new file mode 100644 index 00000000000000..a37eecd8c8eea1 --- /dev/null +++ b/docs/joinus/debug.md @@ -0,0 +1,26 @@ +--- +sidebarDepth: 0 +--- +# 调试 + +当调试代码时,除了使用 `console.log` 或将 node 进程附加到调试器,您还可以将自定义对象提供给 ctx.state.json 来进行调试。 + +## 使用 `ctx.state.json` + +要将自定义对象传递给 ctx.state.json 进行调试,请跟随以下步骤: + +1. 创建自定义对象。 +2. 将对象传递给 `ctx.state.json`。 +3. 访问相应的路由 + `.debug.json` 来查看您的对象。例如,如果您想调试 `/furstar/characters/en`,您可以访问 URL:`/furstar/characters/en.debug.json`。 + +以下是来自 [furstar/index.js](https://github.com/DIYgod/RSSHub/blob/master/lib/v2/furstar/index.js) 的使用 `ctx.state.json` 的示例: + +```js +const info = utils.fetchAllCharacters(res.data, base); + +ctx.state.json = { + info, +}; +``` + +在上面的示例中,我们将 `info` 对象传递给 `ctx.state.json`,然后可以使用相应的路由 + `.debug.json` 来访问它。 diff --git a/docs/joinus/new-radar.md b/docs/joinus/new-radar.md new file mode 100644 index 00000000000000..a07e968ee455de --- /dev/null +++ b/docs/joinus/new-radar.md @@ -0,0 +1,282 @@ +# 提交新的 RSSHub Radar 规则 + +如果需要查看新规则的结果,建议您安装浏览器扩展程序。您可以在 [参与我们](/joinus/quick-start.html#ti-jiao-xin-de-rsshub-radar-gui-ze) 页面下载适合您浏览器的扩展程序。 + +## 编写规则 + +要制作新的 RSSHub Radar 规则,需要在 `/lib/v2/` 目录下,相应的域名空间创建 `radar.js` 文件。下面以制作 `GitHub 仓库 Issues` 的 RSS 源为例,详见此处。编写的代码应如下所示: + +```js +module.exports = { + 'github.com': { + _name: 'GitHub', + '.': [ + { + title: '仓库 Issues', + docs: 'https://docs.rsshub.app/programming.html#github', + source: ['/:user/:repo/issues/:id', '/:user/:repo/issues', '/:user/:repo'], + target: '/github/issue/:user/:repo', + }, + ], + }, +}; +``` + +## 顶层对象键 + +对象键是域名本身,不含任何子域名、URL 路径或协议。 + +![URL 构成](https://wsrv.nl/?url=https://enwpgo.files.wordpress.com/2022/10/image-30.png&output=webp) + +在此示例中,域名为 `github.com`,对象键则为 `github.com`。 + +## 内部对象键 + +第一个内部对象键是 `_name`,是网站的名称。这应与路由文档的二级标题 (`##`) 相同。在此示例中是 `GitHub`。 + +其余的内部对象键是网站的子域名。如果要匹配的网站没有子域名,或者想同时匹配 `www.example.com` 和 `example.com`,则应使用 `'.'`。在此示例中,我们将使用 `'.'`,因为我们希望匹配 `github.com`。请注意,每个子域名键应返回**一个对象数组**。 + + + + +```js{4} +module.exports = { + 'github.com': { + _name: 'GitHub', + '.': [ + { + title: '...', + docs: '...', + source: ['/...'], + target: '/...', + }, + ], + }, +}; +``` + + + + +```js{4} +module.exports = { + 'github.com': { + _name: 'GitHub', + abc: [ + { + title: '...', + docs: '...', + source: ['/...'], + target: '/...', + }, + ], + }, +}; +``` + + + + +```js{4} +module.exports = { + 'github.com': { + _name: 'GitHub', + 'abc.def': [ + { + title: '...', + docs: '...', + source: ['/...'], + target: '/...', + }, + ], + }, +}; +``` + + + + +### `title` + +标题是*必填*字段,应与路由文档的三级标题 (`###`) 相同。在此示例中,它是`仓库 Issues`。在 `title` 中无须重复网站名称 (`_name`),即 `GitHub`。 + +### `docs` + +文档链接也是*必填*字段。在这种情况下,`GitHub 仓库 Issues` 的文档链接将是 `https://docs.rsshub.app/programming.html#github`。请注意,URL hash 应位于二级标题 (`##`) 处,而不是三级标题 (`###`) `https://docs.rsshub.app/programming.html#github-cang-ku-issues`。 + +### `source` + +source 是*可选*字段,应指定 URL 路径。如果不想匹配任何 URL 路径,请将其留空。它只会出现在 RSSHub Radar 浏览器扩展程序的`适用于当前网站的 RSSHub`选项中。 + +source 应为一个字符串数组。例如,如果 `GitHub 仓库 Issues` 的 source 是 `/:user/:repo`,则意味着当您访问 `https://github.com/DIYgod/RSSHub` 时将匹配 `/:user/:repo`,此时返回的结果 params 将是:`{user: 'DIYgod', repo: 'RSSHub'}`。浏览器扩展程序使用这些参数根据 target 字段建立 RSSHub 订阅地址。 + +::: warning 注意 +如果要提取的值在 URL 参数或 URL hash 中,请使用 target 函数而不是 source 字段。 此外,请记住,source 字段仅匹配 URL 路径,而不匹配 URL 的任何其他部分。 +::: + +您也可以使用 `*` 符号执行通配符匹配。请注意,此处的语法与 [path-to-regexp](https://github.com/pillarjs/path-to-regexp) 不同。例如,`/:user/:repo/*` 将匹配 `https://github.com/DIYgod/RSSHub/issues` 和 `https://github.com/DIYgod/RSSHub/issues/1234`。如果要对匹配结果进行命名,可以在 `*` 符号后放置变量名。例如,`/user/:repo/*path`,在此情况下,`path` 将是 `issues` 和 `issues/1234`。 + +### `target` + +目标是**可选**字段,并用于生成 RSSHub 订阅地址,它可以接受字符串或函数作为输入。如果你不想建立 RSSHub 订阅地址,可以将此字段留空。 + +以 `GitHub 仓库 Issues` 为例,在 RSSHub 文档中相应的路由为 `/github/issue/:user/:repo`。 + +在将 source 路径中的 `user` 匹配为 `DIYgod`,`repo` 匹配为 `RSSHub` 后,RSSHub 路由中的 `:user` 将被替换为 `DIYgod`,`:repo` 将被替换为 `RSSHub`,结果为 `/github/issue/DIYgod/RSSHub`。 + +#### `target` 函数 + +如果 source 路径不能匹配 RSSHub 路由的期望参数,则可以将 target 作为函数使用,与 `params`、`url` 和 `document` 参数一起使用。其中,`params` 参数包含 `source` 字段匹配到的参数,而 `url` 参数是当前的网页 URL 字符串,`document` 参数是 [document 接口](https://developer.mozilla.org/docs/Web/API/document)。 + +需要注意的是,`target` 函数在沙盒中运行,对 `document` 的任何更改都不会反映在网页中。 + +下面是使用 `target` 字段作为函数的两个示例: + + + + +```js{9} +module.exports = { + 'github.com': { + _name: 'GitHub', + '.': [ + { + title: '仓库 Issues', + docs: 'https://docs.rsshub.app/en/programming.html#github', + source: ['/:user/:repo/issues/:id', '/:user/:repo/issues', '/:user/:repo'], + target: (params) => `/github/issue/${params.user}/${params.repo}`, + }, + ], + }, +}; +``` + + + + +```js{9} +module.exports = { + 'github.com': { + _name: 'GitHub', + '.': [ + { + title: '仓库 Issues', + docs: 'https://docs.rsshub.app/en/programming.html#github', + source: ['/:user/:repo'], + target: (_, url) => `/github/issue${new URL(url).pathname}` + }, + ], + }, +}; +``` + + + + +两个示例将返回与 [第一个示例](/joinus/new-radar.html#bian-xie-gui-ze) 相同的 RSSHub 订阅地址。 + +### RSSBud + +[RSSBud](https://github.com/Cay-Zhang/RSSBud) 支持 RSSHub Radar 的规则并且也会自动更新,但是请注意: + +- 使用 `'.'` 子域名可以使 RSSBud 支持常见的移动端子域名,例如 `m`/`mobile`。 +- 在 `target` 中使用 `document` 的规则并不适用于 RSSBud:RSSBud 不是浏览器扩展程序,它只能获取和分析网站的 URL,不能运行 JavaScript。 + +### 补充文档 + +[如前所述](/joinus/new-rss/add-docs.html#wen-dang-shi-li-qi-ta-zu-jian),在 RSSHub 文档添加 radar="1" 将显示“支持浏览器扩展”的徽章。如果规则还与 RSSBud 兼容,则添加 rssbud="1" 将显示“支持 RSSBud”的徽章。 + +## 调试 Radar 规则 + +你可以在浏览器中的 RSSHub Radar 扩展设置中调试你的 radar 规则。首先,打开设置并切换到 “规则列表” 选项页。然后滚动到页面底部,您会看到一个文本框。在这里,您可以使用您的新规则替换旧规则以进行调试。 + +如果担心失去原来的 RSSHub Radar 规则,那就不要担心,如果你在设置页面中点击“立即更新”按钮,它将会被恢复。 + +以下是几个可以用来调试的 radar 规则示例: + +```js +({ + 'github.com': { + _name: 'GitHub', + '.': [ + { + title: '仓库 Issues', + docs: 'https://docs.rsshub.app/en/programming.html#github', + source: ['/:user/:repo/issues/:id', '/:user/:repo/issues', '/:user/:repo'], + target: '/github/issue/:user/:repo', + }, + ], + }, +}) +``` + +::: details 其他示例 + +```js +({ + 'bilibili.com': { + _name: 'bilibili', + www: [ + { + title: '分区视频', + docs: 'https://docs.rsshub.app/social-media.html#bilibili', + source: '/v/*tpath', + target: (params) => { + let tid; + switch (params.tpath) { + case 'douga/mad': + tid = '24'; + break; + default: + return false; + } + return `/bilibili/partion/${tid}`; + }, + }, + ], + }, + 'twitter.com': { + _name: 'Twitter', + '.': [ + { + // for twitter.com + title: '用户时间线', + docs: 'https://docs.rsshub.app/social-media.html#twitter', + source: '/:id', + target: (params) => { + if (params.id !== 'home') { + return '/twitter/user/:id'; + } + }, + }, + ], + }, + 'pixiv.net': { + _name: 'Pixiv', + www: [ + { + title: '用户收藏', + docs: 'https://docs.rsshub.app/social-media.html#pixiv', + source: '/bookmark.php', + target: (params, url) => `/pixiv/user/bookmarks/${new URL(url).searchParams.get('id')}`, + }, + ], + }, + 'weibo.com': { + _name: '微博', + '.': [ + { + title: '博主', + docs: 'https://docs.rsshub.app/social-media.html#%E5%BE%AE%E5%8D%9A', + source: ['/u/:id', '/:id'], + target: (params, url, document) => { + const uid = document && document.documentElement.innerHTML.match(/\$CONFIG\['oid']='(\d+)'/)[1]; + return uid ? `/weibo/user/${uid}` : ''; + }, + }, + ], + }, +}) +``` + +::: diff --git a/docs/joinus/new-rss/add-docs.md b/docs/joinus/new-rss/add-docs.md new file mode 100644 index 00000000000000..52d5b86d8b8bf7 --- /dev/null +++ b/docs/joinus/new-rss/add-docs.md @@ -0,0 +1,181 @@ +--- +sidebarDepth: 2 +--- + +# 添加文档 + +现在我们完成了代码,是时候为您的路由添加文档了。在 [文档(/docs/)](https://github.com/DIYgod/RSSHub/blob/master/docs/en)中打开相应的文件,本例中是 `docs/programming.md`。您可以通过运行以下命令实时预览文档: + + + + +```bash +yarn docs:dev +``` + + + + +```bash +npm run docs:dev +``` + + + + +文档使用 Markdown 编写,并使用 [VuePress v1](https://v1.vuepress.vuejs.org) 渲染。 + +要为您的路由添加文档,请使用 Vue 组件。它们类似于 HTML 标签。以下是最常用的组件: + +- `author`:路由维护者,用单个空格分隔。应与 [`maintainer.js`](/joinus/new-rss/before-start.html#li-jie-ji-chu-zhi-shi-maintainer-js) 相同 +- `example`:路由示例,以 `/` 开头 +- `path`:路由,应与添加命名空间后 [maintainer.js](/joinus/new-rss/before-start.html#li-jie-ji-chu-zhi-shi-maintainer-js) 中的键相同。在之前的教程中,它为 `/github/issue/:user/:repo?` +- `:paramsDesc`:路由参数描述,以字符串数组形式,支持 Markdown。 + - 描述必须按照它们在路由中出现的顺序。 + - 描述的数量**应**与 `path` 中的参数数量匹配。如果漏掉一个描述,则构建过程会失败。 + - 以 `?`,`*` 或 `+` 结尾的路由参数将自动分别标记为`可选`,`零个或多个`或`一个或多个`,无须再次提及。 + - 没有符号后缀的路由参数将自动标记为`必选` + - 如果参数是可选的,请提及其默认值。 + +## 文档示例 + +### 仓库 Issues(无参数) + +```vue + +``` + +--- + + + +--- + +### 仓库 Issues(多个参数) + +```vue + +``` + +--- + + + +--- + +### 关键词(带表格的说明) + +```vue + + +| 只看非 R18 内容 | 只看 R18 内容 | 不过滤 | +| ------------ | -------- | ------------ | +| safe | r18 | 空或其他任意值 | + + +``` + +--- + + + +| 只看非 R18 内容 | 只看 R18 内容 | 不过滤 | +| ------------ | -------- | ------------ | +| safe | r18 | 空或其他任意值 | + + + +--- + +### 自定义容器 + +如果您想提供关于路由的更多信息,可以使用这些自定义容器: + +```md +::: tip 提示标题 +这是一个提示。 +::: + +::: warning 警告标题 +这是一个警告。 +::: + +::: danger 危险标题 +这是一条危险的警告。 +::: + +::: details 详细标题 +这是一个详细块。 +::: +``` + +--- + +::: tip 提示标题 +这是一个提示。 +::: + +::: warning 警告标题 +这是一个警告。 +::: + +::: danger 危险标题 +这是一条危险的警告。 +::: + +::: details 详细标题 +这是一个详细块。 +::: + +--- + +### 其他组件 + +除了路由组件之外,还有几个组件可用于提供有关路径的更多信息: + +- `anticrawler`:如果目标网站有反爬机制,则设置为 `1`。 +- `puppeteer`:如果源使用 puppeteer 抓取,则设置为 `1`。 +- `radar`:如果此源有相应的 Radar 规则,则设置为 `1`。 +- `rssbud`:如果 Radar 规则与 RSSBud 兼容,则设置为 `1`。 +- `selfhost`:如果 RSS 源需要通过环境变量进行额外配置,则设置为 `1`。 +- `supportBT`:如果支持被 BitTorrent 客户端识别,则设置为 `1`。 +- `supportPodcast`:如果源支持播客,则设置为 `1`。 +- `supportScihub`:如果源支持 Sci-Hub,则设置为 `1`。 + +通过添加这些组件,您可以向用户提供有用的信息,并使其更易于理解和使用您的路由。将这些组件添加到路由文档中将在其前面添加一个徽章。 + +```vue + +``` + +--- + + + +--- + +## 其他事项 + +- 为路由添加文档时,请使用三级标题(`###`)。如果路由文档没有二级标题,则添加二级标题(`##`)。 +- 在每个标题和内容之间留一个空行。这有助于确保文档可以成功构建。 +- 如果文档包含大型表格,建议将其放入 [details 容器](#wen-dang-shi-li-zi-ding-yi-rong-qi) 中。 +- 组件可以有两种写法:自闭合标签形式(``)或成对标签形式(`...`)。 +- **别忘了关闭标签!** +- 在提交 Pull Request 之前,请务必运行以下命令检查和格式化您的代码: + + + + +```bash +yarn format +``` + + + + +```bash +npm run format +``` + + + diff --git a/docs/joinus/new-rss/before-start.md b/docs/joinus/new-rss/before-start.md new file mode 100644 index 00000000000000..c62fd0fa29ce5f --- /dev/null +++ b/docs/joinus/new-rss/before-start.md @@ -0,0 +1,153 @@ +--- +sidebarDepth: 2 +--- +# 开始之前 + +在本教程中,我们将通过制作一个 [GitHub 仓库 Issues](/programming.html#github-cang-ku-issues) 的 RSS 源为例,向您展示制作 RSS 源的过程。 + +## 安装依赖 + +开始之前,您需要安装 RSSHub 的依赖项。您可以在 RSSHub 的根目录下运行以下命令来完成安装: + + + + +```bash +yarn +``` + + + + +```bash +npm install +``` + + + + +## 开始调试 + +一旦您成功安装了依赖,您可以通过运行以下命令来开始调试 RSSHub: + + + + +```bash +yarn dev +``` + + + + +```bash +npm run dev +``` + + + + +请务必密切关注控制台输出的任何错误消息或其他有用的信息,这些信息可以帮助您诊断和解决问题。另外,如果您遇到任何困难,不要犹豫向 RSSHub 文档或社区寻求帮助。 + +要查看您所做更改的结果,请在浏览器中打开 `http://localhost:1200`。您将能够在浏览器中自动反映您对代码的更改。 + +## 遵循路由规范 + +确保所有新的 RSS 源路由均遵循 [路由规范](/joinus/script-standard.html) 非常重要。不遵循规范可能导致您的 Pull Request 在合理的时间内无法合并。 + +[路由规范](/joinus/script-standard.html) 提供了制作高质量和可靠源代码的指导方针。通过遵循这些指南,您可以确保您的 RSS 源按照预期工作,并且易于其他社区维护者阅读。 + +在提交您的 Pull Request 之前,请仔细阅读 [路由规范](/joinus/script-standard.html),并确保您的代码符合所有要求。这将有助于加快审查过程。 + +## 创建命名空间 + +制作新的 RSS 路由的第一步是创建命名空间。命名空间应该与您制作 RSS 源的主要网站的二级域名**相同**。例如,如果您正在为 制作 RSS 源,第二级域名是 `github`。因此,您应该在 `lib/v2` 下创建名为 `github` 的文件夹,作为您的 RSS 路由的命名空间。 + +::: tip 提示 +在创建命名空间时,避免为同一命名空间的创建多个变体。例如,如果您为 `yahoo.co.jp` 和 `yahoo.com` 制作 RSS 源,则应该使用单个命名空间 `yahoo`,而不是创建多个命名空间如 `yahoo-jp`、`yahoojp`、`yahoo.jp`、`jp.yahoo`、`yahoocojp` 等。 +::: + +## 理解基础知识 + +### router.js + +一旦您为 RSS 路由创建了命名空间,下一步就是在 `router.js` 中注册它。 + +例如,如果您为 [GitHub 仓库 Issues](/programming.html#github-cang-ku-issues) 制作 RSS 源,并且假设您希望用户输入 GitHub 用户名和仓库名,如果他们没有输入仓库名,则返回到 `RSSHub`,您可以使用以下代码在 `github/router.js` 中注册您的新 RSS 路由: + + + + +```js{2} +module.exports = (router) => { + router.get('/issue/:user/:repo?', require('./issue')); +}; +``` + + + + +```js{2} +module.exports = function (router) { + router.get('/issue/:user/:repo?', require('./issue')); +}; +``` + + + + +在 `router.js` 中注册您的新 RSS 路由时,您可以定义路由路径并指定要执行的相应函数。在上面的代码中,`router.get()` 方法用于指定新的 RSS 路由的 HTTP 方法和路径。`router.get()` 的第一个参数是使用 [path-to-regexp](https://github.com/pillarjs/path-to-regexp) 语法的路由路径。第二个参数是您新的 RSS 规则 `issue.js` 中导出的函数。您可以省略 `.js` 扩展名。 + +在上面的示例中,`issue` 是一个精确匹配,`:user` 是一个必需参数,`:repo?` 是一个可选参数。`?` 在 `:repo` 之后表示该参数是可选的。如果用户没有输入仓库名,则会返回到您代码中指定的内容(这里是 `RSSHub`)。 + +一旦您定义了路由路径,您可以从 `ctx.params` 对象中获取参数的值。例如,如果用户访问了 `/github/issue/DIYgod/RSSHub`,您可以分别从 `ctx.params.user` 和 `ctx.params.repo` 中获取 `user` 和 `repo` 的值。例如,如果用户访问了 `/github/issue/DIYgod/RSSHub`,则 `ctx.params.user` 和 `ctx.params.repo` 将分别为 `DIYgod` 和 `RSSHub`。 + +**请注意,值的类型将是 String 或 undefined。** + +您可以使用 `*` 或 `+` 符号来匹配路径的其余部分,例如 `/some/path/:variable*`。请注意,`*` 和 `+` 分别意味着“零个或多个”和“一个或多个”。您还可以使用模式,例如 `/some/path/:variable(\\d+)?`,甚至是正则表达式。 + +::: tip 提示 +有关 `router` 的更高级用法,请参阅 [@koa/router API 参考文档](https://github.com/koajs/router/blob/master/API.md)。 +::: + +### maintainer.js + +该文件用于存储 RSS 路由的维护者信息。您可以将您的 GitHub 用户名添加到该值数组中。请注意,此处的键应与 `router.js` 中的路径完全匹配: + +```js{2} +module.exports = { + '/issue/:user/:repo?': ['DIYgod'], +}; +``` + +`maintainer.js` 文件有助于跟踪负责维护 RSS 路由的人员。当用户遇到 RSS 路由的问题时,他们可以联系此文件中列出的维护者。 + +### `templates` 文件夹 + +`templates` 文件夹包含您的新 RSS 路由的模板。如果您不需要呈现任何自定义 HTML 内容,可以跳过此文件夹。 + +每个模板文件应该有一个 `.art` 文件扩展名。 + +### radar.js + +文件可以帮助用户在使用 [RSSHub Radar](https://github.com/DIYgod/RSSHub-Radar) 或其他兼容其格式的软件时订阅您的新 RSS 路由。我们将在后面的部分更多介绍。 + +### 您的新 RSS 路由 `issue.js` + +现在您可以开始 [编写](/joinus/new-rss/start-code.html) 新的 RSS 路由了。 + +## 获取数据 + +要为新的 RSS 路由获取数据,通常需要使用 [got](https://github.com/sindresorhus/got) 进行 HTTP 请求到 API 或网页。在某些情况下,您可能需要使用 [puppeteer](https://github.com/puppeteer/puppeteer) 模拟浏览器并呈现网页以获取数据。 + +您获取的数据通常会以 JSON 或 HTML 格式呈现。如果您需要处理的是 HTML,可以使用 [cheerio](https://github.com/cheeriojs/cheerio) 进行进一步处理。 + +以下是推荐的数据获取方法列表,按优先顺序排列: + +1. **通过 API**:这是获取数据最推荐的方法,因为 API 通常比从 HTML 网页中提取数据更稳定和更快。 + +2. **通过 got 从 HTML 获取数据**:如果 API 不可用,则可以尝试从 HTML 网页中提取数据。这通常是您大部分时候使用的方法。请注意,如果 HTML 包含嵌入 JSON,则应该使用它而不是其他 HTML 元素。 + +3. **通用配置路由**:通用配置路由是一条特殊的路由,它可以通过 cheerio(CSS 选择器和 jQuery 函数)读取 JSON 数据轻松生成 RSS。 + +4. **Puppeteer**:在某些罕见情况下,您可能需要使用 puppeteer 模拟浏览器并获取数据。这是最不推荐的方法,只有在绝对必要时才应该使用,例如网页带有浏览器完整性检查或强加密。如果可能,请使用其他方法,例如 API 请求或使用 [got](https://github.com/sindresorhus/got) 或 [cheerio](https://github.com/cheeriojs/cheerio) 直接从 HTML 页面中提取数据。 diff --git a/docs/joinus/new-rss/prerequisites.md b/docs/joinus/new-rss/prerequisites.md new file mode 100644 index 00000000000000..f9fae3764e164f --- /dev/null +++ b/docs/joinus/new-rss/prerequisites.md @@ -0,0 +1,39 @@ +# 准备工作 + +在开始编写新的 RSS 规则之前,确保您的开发环境已正确配置很重要。 + +## 安装 Node.js + +为了能够编写新的 RSS 规则,您必须首先安装 Node.js。RSSHub 使用 Node.js 运行其代码以及制作 RSS 订阅源,需要 Node v16 或更高版本。您可以从 [这里](https://nodejs.org/en/download) 下载最新的 Node.js LTS 版本。 + +在 Windows 系统下,您可以下载安装程序并按照安装程序的步骤进行操作。记得勾选安装 **原生模块的工具(Tools for Native Modules)** 选项。 + +在 macOS 系统下,您可以从 Node.js 网站下载安装程序,或者使用 [Homebrew](https://brew.sh) 命令 `brew install node` 安装 Node.js。 + +在 Linux 系统下,您可以参考 [这个页面](https://nodejs.org/en/download/package-manager) 决定如何安装 Node.js。 + +## 安装代码编辑器 + +编写代码需要一个代码编辑器。如果您已经有一个,您可以跳过这一部分。如果您还没有一个编辑器,可以从以下列表中选择一个: + +- [Visual Studio Code](https://code.visualstudio.com) +- [WebStorm](https://www.jetbrains.com/webstorm) +- [Neovim](https://neovim.io) +- [Sublime Text](https://www.sublimetext.com) + +为了加速开发过程并更容易维护代码风格的一致性,可以为您选择的代码编辑器安装一些适当的扩展。在本指南的后半部分,我们将使用 Visual Studio Code 作为示例,您可以安装以下扩展: + +- [Art Template Helper](https://marketplace.visualstudio.com/items?itemName=ZihanLi.at-helper)(为 RSSHub 使用的一种模板引擎提供语法高亮) +- [EditorConfig for VS Code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig)(保持在不同的 IDE 中的一致的代码风格) +- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)(识别并修复代码中的常见错误) +- [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)(使您的代码更易读和更一致地格式化) + +### 云托管的开发环境 + +如果您不想在计算机上安装 Node.js 和代码编辑器,您可以使用云托管的开发环境。您可以使用 [GitHub Codespaces](https://codespace.new) 或 [Gitpod](https://www.gitpod.io)。只需点击以下按钮即可启动新的工作区: + +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=master&repo=127769231&machine=basicLinux32gb&devcontainer_path=.devcontainer%2Fdevcontainer.json&location=SouthEastAsia) + +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/DIYgod/RSSHub) + +有关如何使用 [GitHub Codespaces](https://codespace.new) 或 [Gitpod](https://www.gitpod.io) 的更多信息,请参见 [GitHub 文档](https://docs.github.com/codespaces) 和 [Gitpod 文档](https://www.gitpod.io/docs)。 diff --git a/docs/joinus/new-rss/start-code.md b/docs/joinus/new-rss/start-code.md new file mode 100644 index 00000000000000..401ee86dee15a5 --- /dev/null +++ b/docs/joinus/new-rss/start-code.md @@ -0,0 +1,769 @@ +--- +sidebarDepth: 2 +--- +# 制作自己的 RSSHub 路由 + +如前所述,我们以 [GitHub 仓库 Issues](/programming.html#github-cang-ku-issues) 为例制作 RSS 源。我们将展示前面提到的四种数据获取方法: + +1. [通过 API](#tong-guo-api) +2. [通过 got 从 HTML 获取数据](#tong-guo-got-cong-html-huo-qu-shu-ju) +3. [使用通用配置路由](#shi-yong-tong-yong-pei-zhi-lu-you) +4. [使用 puppeteer](#shi-yong-puppeteer) + +## 通过 API + +### 查看 API 文档 + +不同的站点有不同的 API。您可以查看要为其制作 RSS 源的站点的 API 文档。在本例中,我们将使用 [GitHub Issues API](https://docs.github.com/zh/rest/issues/issues#list-repository-issues)。 + +### 创建主文件 + +打开您的代码编辑器并创建一个新文件。由于我们要为 GitHub 仓库 Issues 制作 RSS 源,因此建议将文件命名为 `issue.js`。 + +以下是让您开始的基本代码: + + + + +```js +// 导入所需模组 +const got = require('@/utils/got'); // 自订的 got +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + // 在此处编写您的逻辑 + + ctx.state.data = { + // 在此处输出您的 RSS + }; +}; +``` + + + + +### 获取用户输入 + +如前所述,我们需要从用户输入中获取 GitHub 用户名和仓库名称。如果请求 URL 中未提供仓库名称,则应默认为 `RSSHub`。您可以使用以下代码实现: + + + + +```js{2} +module.exports = async (ctx) => { + const { user, repo = 'RSSHub' } = ctx.params; + + ctx.state.data = { + // 在此处输出您的 RSS + }; +}; +``` + + + + +```js{2,3} +module.exports = async (ctx) => { + const user = ctx.params.user; + const repo = ctx.params.repo ?? 'RSSHub'; + + ctx.state.data = { + // 在此处输出您的 RSS + }; +}; +``` + + + + +这两个代码片段都执行相同的操作。第一个使用对象解构将 `user` 和 `repo` 变量赋值,而第二个使用传统赋值和空值合并运算符在请求 URL 中未提供它的情况下将 `repo` 变量分配默认值 `RSSHub`。 + +### 从 API 获取数据 + +在获取用户输入后,我们可以使用它向 API 发送请求。大多数情况下,您需要使用 `@/utils/got` 中的 `got`(一个自订的 [got](https://www.npmjs.com/package/got) 包装函数)发送 HTTP 请求。有关更多信息,请参阅 [got 文档](https://github.com/sindresorhus/got/tree/v11#usage)。 + + + + +```js{3-14} +module.exports = async (ctx) => { + const { user, repo = 'RSSHub' } = ctx.params; + // 发送 HTTP GET 请求到 API 并解构返回的数据对象 + const { data } = await got(`https://api.github.com/repos/${user}/${repo}/issues`, { + headers: { + // 为简单起见,此示例使用 HTML 而不是推荐的 'application/vnd.github+json', + // 因后者返回 Markdown 并需要进一步处理 + accept: 'application/vnd.github.html+json', + }, + searchParams: { + // 这允许用户设置条数限制 + per_page: ctx.query.limit ? parseInt(ctx.query.limit, 10) : 30, + }, + }); + + ctx.state.data = { + // 在此处输出您的 RSS + }; +}; +``` + + + + +```js{4-14} +module.exports = async (ctx) => { + const user = ctx.params.user; + const repo = ctx.params.repo ?? 'RSSHub'; + // 发送 HTTP GET 请求到 API + const response = await got(`https://api.github.com/repos/${user}/${repo}/issues`, { + headers: { + accept: 'application/vnd.github.html+json', + }, + searchParams: { + per_page: ctx.query.limit ? parseInt(ctx.query.limit, 10) : 30, + }, + }); + // response.data 是上述请求返回的数据对象 + const data = response.data; + + ctx.state.data = { + // 在此处输出您的 RSS + }; +}; +``` + + + + +### 生成 RSS 源 + +一旦我们从 API 获取到数据,我们需要进一步处理它以生成符合 RSS 规范的 RSS 源。具体来说,我们需要提取源标题、源链接、文章标题、文章链接、文章正文和文章发布日期。 + +为此,我们可以将相关数据赋值给 `ctx.state.data` 对象,RSSHub 的中间件将处理其余部分。 + +以下是应有的最终代码: + + + + +```js{16-30,32-39} +const got = require('@/utils/got'); +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + const { user, repo = 'RSSHub' } = ctx.params; + + const { data } = await got(`https://api.github.com/repos/${user}/${repo}/issues`, { + headers: { + accept: 'application/vnd.github.html+json', + }, + searchParams: { + per_page: ctx.query.limit ? parseInt(ctx.query.limit, 10) : 30, + }, + }); + + // 从 API 响应中提取相关数据 + const items = data.map((item) => ({ + // 文章标题 + title: item.title, + // 文章链接 + link: item.html_url, + // 文章正文 + description: item.body_html, + // 文章发布日期 + pubDate: parseDate(item.created_at), + // 如果有的话,文章作者 + author: item.user.login, + // 如果有的话,文章分类 + category: item.labels.map((label) => label.name), + })); + + ctx.state.data = { + // 源标题 + title: `${user}/${repo} issues`, + // 源链接 + link: `https://github.com/${user}/${repo}/issues`, + // 源文章 + item: items, + }; +}; +``` + + + + +```js{16-36} +const got = require('@/utils/got'); +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + const { user, repo = 'RSSHub' } = ctx.params; + + const { data } = await got(`https://api.github.com/repos/${user}/${repo}/issues`, { + headers: { + accept: 'application/vnd.github.html+json', + }, + searchParams: { + per_page: ctx.query.limit ? parseInt(ctx.query.limit, 10) : 30, + }, + }); + + ctx.state.data = { + // 源标题 + title: `${user}/${repo} issues`, + // 源链接 + link: `https://github.com/${user}/${repo}/issues`, + // 遍历所有此前获取的数据 + item: data.map((item) => ({ + // 文章标题 + title: item.title, + // 文章链接 + link: item.html_url, + // 文章正文 + description: item.body_html, + // 文章发布日期 + pubDate: parseDate(item.created_at), + // 如果有的话,文章作者 + author: item.user.login, + // 如果有的话,文章分类 + category: item.labels.map((label) => label.name), + })); + }; +}; +``` + + + + +## 通过 got 从 HTML 获取数据 + +### 创建主文件 + +打开您的代码编辑器并创建一个新文件。由于我们要为 GitHub 仓库 Issues 制作 RSS 源,因此建议将文件命名为 `issue.js`。 + +以下是让您开始的基本代码: + +```js +// 导入必要的模组 +const got = require('@/utils/got'); // 自订的 got +const cheerio = require('cheerio'); // 可以使用类似 jQuery 的 API HTML 解析器 +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + // 在此处编写您的逻辑 + + ctx.state.data = { + // 在此处输出您的 RSS + }; +}; +``` + +`parseDate` 函数是 RSSHub 提供的一个工具函数,在代码的后面我们会用到它来解析日期。 + +您需要添加自己的代码来从 HTML 文档中提取数据、处理数据并以 RSS 格式输出。在下一步中,我们将详细介绍此过程的细节。 + +### 获取用户输入 + +如前所述,我们需要从用户输入中获取 GitHub 用户名和仓库名称。如果请求 URL 中未提供仓库名称,则应默认为 `RSSHub`。您可以使用以下代码实现: + +```js{2-3} +module.exports = async (ctx) => { + // 从 URL 参数中获取用户名和仓库名称 + const { user, repo = 'RSSHub' } = ctx.params; + + ctx.state.data = { + // 在此处输出您的 RSS + }; +}; +``` + +在这段代码中,`user` 将被设置为 `user` 参数的值,如果存在 `repo` 参数,则 `repo` 将被设置为该参数的值,否则为 `RSSHub`。 + +### 从网页获取数据 + +在获取了用户输入之后,我们需要向网页发起请求,以检索所需的信息。在大多数情况下,我们将使用 `@/utils/got` 中的 `got`(一个自订的 [got](https://www.npmjs.com/package/got) 包装函数)发送 HTTP 请求。您可以在 [got 文档](https://github.com/sindresorhus/got/tree/v11#usage) 中找到有关如何使用 got 的更多信息。 + +首先,我们将向 API 发送 HTTP GET 请求,并将 HTML 响应加载到 Cheerio 中,Cheerio 是一个帮助我们解析和操作 HTML 的库。 + +```js{5-6} + const baseUrl = 'https://github.com'; + const { user, repo = 'RSSHub' } = ctx.params; + + // 注意,".data" 属性包含了请求返回的目标页面的完整 HTML 源代码 + const { data: response } = await got(`${baseUrl}/${user}/${repo}/issues`); + const $ = cheerio.load(response); +``` + +接下来,我们将使用 Cheerio 选择器选择相关的 HTML 元素,解析我们需要的数据,并将其转换为数组。 + +```js{3-21} + // 我们使用 Cheerio 选择器选择所有带类名“js-navigation-container”的“div”元素, + // 其中包含带类名“flex-auto”的子元素。 + const item = $('div.js-navigation-container .flex-auto') + // 使用“toArray()”方法将选择的所有 DOM 元素以数组的形式返回。 + .toArray() + // 使用“map()”方法遍历数组,并从每个元素中解析需要的数据。 + .map((item) => { + item = $(item); + const a = item.find('a').first(); + return { + title: a.text(), + // `link` 需要一个绝对 URL,但 `a.attr('href')` 返回一个相对 URL。 + link: `${baseUrl}${a.attr('href')}`, + pubDate: parseDate(item.find('relative-time').attr('datetime')), + author: item.find('.opened-by a').text(), + category: item + .find('a[id^=label]') + .toArray() + .map((item) => $(item).text()), + }; + }); + + ctx.state.data = { + // 在此处输出您的 RSS + }; +``` + +### 生成 RSS 源 + +一旦我们从 API 获取到数据,我们需要进一步处理它以生成符合 RSS 规范的 RSS 源。具体来说,我们需要提取源标题、源链接、文章标题、文章链接、文章正文和文章发布日期。 + +为此,我们可以将相关数据赋值给 `ctx.state.data` 对象,RSSHub 的中间件将处理其余部分。 + +以下是应有的最终代码: + +```js{29-36} +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + const baseUrl = 'https://github.com'; + const { user, repo = 'RSSHub' } = ctx.params; + + const { data: response } = await got(`${baseUrl}/${user}/${repo}/issues`); + const $ = cheerio.load(response); + + const item = $('div.js-navigation-container .flex-auto') + .toArray() + .map((item) => { + item = $(item); + const a = item.find('a').first(); + return { + title: a.text(), + link: `${baseUrl}${a.attr('href')}`, + pubDate: parseDate(item.find('relative-time').attr('datetime')), + author: item.find('.opened-by a').text(), + category: item + .find('a[id^=label]') + .toArray() + .map((item) => $(item).text()), + }; + }); + + ctx.state.data = { + // 源标题 + title: `${user}/${repo} issues`, + // 源链接 + link: `${baseUrl}/${user}/${repo}/issues`, + // 源文章 + item: items, + }; +}; +``` + +### 更好的阅读体验 + +上述的代码仅针对每个订阅项提供部分信息。为了提供更好的阅读体验,我们可以在每个订阅项中添加完整的文章,例如每个 GitHub Issue 的正文。 + +以下是更新后的代码: + +```js{12,29-43,48} +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + const baseUrl = 'https://github.com'; + const { user, repo = 'RSSHub' } = ctx.params; + + const { data: response } = await got(`${baseUrl}/${user}/${repo}/issues`); + const $ = cheerio.load(response); + + const list = $('div.js-navigation-container .flex-auto') + .toArray() + .map((item) => { + item = $(item); + const a = item.find('a').first(); + return { + title: a.text(), + link: `${baseUrl}${a.attr('href')}`, + pubDate: parseDate(item.find('relative-time').attr('datetime')), + author: item.find('.opened-by a').text(), + category: item + .find('a[id^=label]') + .toArray() + .map((item) => $(item).text()), + }; + }); + + const items = await Promise.all( + list.map((item) => + ctx.cache.tryGet(item.link, async () => { + const { data: response } = await got(item.link); + const $ = cheerio.load(response); + + // 选择类名为“comment-body”的第一个元素 + item.description = $('.comment-body').first().html(); + + // 上面每个列表项的每个属性都在此重用, + // 并增加了一个新属性“description” + return item; + }) + ) + ); + + ctx.state.data = { + title: `${user}/${repo} issues`, + link: `https://github.com/${user}/${repo}/issues`, + item: items, + }; +}; + +``` + +现在,这个 RSS 源将具有类似于原始网站的阅读体验。 + +::: tip 提示 +请注意,在先前的部分中,我们仅需向 API 发送一个 HTTP 请求即可获得所需的所有数据。然而,在此部分中,我们需要发送 `1 + n` 个 HTTP 请求,其中 `n` 是从第一个请求获取的文章列表中的数量。 + +部分网站可能不喜欢在短时间内接收大量请求,并返回类似于“429 Too Many Requests”的错误。 +::: + +## 使用通用配置路由 + +### 创建主文件 + +首先,我们需要一些数据: + +1. RSS 来源链接 +2. 数据来源链接 +3. RSS 订阅标题(不是每个文章的标题) + +打开您的代码编辑器并创建一个新文件。由于我们要为 GitHub 仓库 Issues 制作 RSS 源,因此建议将文件命名为 `issue.js`。 + +这是一些基础代码,你可以从这里开始: + +```js +// 导入所需模组 +const buildData = require('@/utils/common-config'); + +module.exports = async (ctx) => { + ctx.state.data = await buildData({ + link: '', // RSS 来源链接 + url: '', // 数据来源链接 + // 此处可以使用变量 + // 如 %xxx% 会被解析为 **params** 中同名变量的值 + title: '%title%', + params: { + title: '', // 标题变量 + }, + }); +}; +``` + +我们的 RSS 订阅源目前缺少内容。必须设置 `item` 才能添加内容。以下是一个示例: + + +```js{15-22} +const buildData = require('@/utils/common-config'); + +module.exports = async (ctx) => { + const { user, repo = 'RSSHub' } = ctx.params; + const link = `https://github.com/${user}/${repo}/issues`; + + ctx.state.data = await buildData({ + link, + url: link, + title: `${user}/${repo} issues`, // 也可以使用 $('head title').text() + params: { + title: `${user}/${repo} issues`, + baseUrl: 'https://github.com', + }, + item: { + item: 'div.js-navigation-container .flex-auto', + // 如果要使用变量,必须使用模板字符串 + title: `$('a').first().text() + ' - %title%'`, // 仅支持像 $().xxx() 这样的 js 语句 + link: `'%baseUrl%' + $('a').first().attr('href')`, // .text() 为获取元素的文本 + // description: ..., 目前没有文章正文 + pubDate: `parseDate($('relative-time').attr('datetime'))`, + }, + }); +}; +``` + +你会发现,此代码与上面的 [从网页获取数据](#tong-guo-got-cong-html-huo-qu-shu-ju-cong-wang-ye-huo-qu-shu-ju) 部分相似。但是,这个 RSS 订阅源不包含 GitHub Issue 的正文。 + +### 获取完整文章 + +要获取每个 GitHub Issue 的正文,你需要添加一些代码。以下是一个示例: + +```js{2-3,25-34} +const buildData = require('@/utils/common-config'); +const got = require('@/utils/got'); +const cheerio = require('cheerio'); + +module.exports = async (ctx) => { + const { user, repo = 'RSSHub' } = ctx.params; + const link = `https://github.com/${user}/${repo}/issues`; + + ctx.state.data = await buildData({ + link, + url: link, + title: `${user}/${repo} issues`, + params: { + title: `${user}/${repo} issues`, + baseUrl: 'https://github.com', + }, + item: { + item: 'div.js-navigation-container .flex-auto', + title: `$('a').first().text() + ' - %title%'`, + link: `'%baseUrl%' + $('a').first().attr('href')`, + pubDate: `parseDate($('relative-time').attr('datetime'))`, + }, + }); + + await Promise.all( + ctx.state.data.item.map((item) => + ctx.cache.tryGet(item.link, async () => { + const { data: resonse } = await got(item.link); + const $ = cheerio.load(resonse); + item.description = $('.comment-body').first().html(); + return item; + }) + ) + ); +}; +``` + +你可以看到,上面的代码与 [前一节](#tong-guo-got-cong-html-huo-qu-shu-ju-geng-hao-de-yue-du-ti-yan) 非常相似,通过添加一些代码它获取了完整文章。建议你尽可能使用 [前一节](#tong-guo-got-cong-html-huo-qu-shu-ju-geng-hao-de-yue-du-ti-yan) 中的方法,因为它比使用 `@/utils/common-config` 更加灵活。 + +## 使用 puppeteer + +使用 Puppeteer 是从网站获取数据的另一种方法。不过,建议您首先尝试 [上述方法](#tong-guo-got-cong-html-huo-qu-shu-ju)。还建议您先阅读 [通过 got 从 HTML 获取数据](#tong-guo-got-cong-html-huo-qu-shu-ju),因为本节是前一节的扩展,不会解释一些基本概念。 + +### 创建主文件 + +创建一个新文件并使用适当的名称保存,例如 `issue.js`。然后,导入所需模组并设置函数的基本结构: + +```js +// 导入所需模组 +const cheerio = require('cheerio'); // 可以使用类似 jQuery 的 API HTML 解析器 +const { parseDate } = require('@/utils/parse-date'); +const logger = require('@/utils/logger'); + +module.exports = async (ctx) => { + // 在此处编写您的逻辑 + + ctx.state.data = { + // 在此处输出您的 RSS + }; +}; +``` + +### 将 got 替换为 puppeteer + +现在,我们将使用 `puppeteer` 代替 `got` 来从网页获取数据。 + + + + +```js{9-33,39-40} +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); +const logger = require('@/utils/logger'); + +module.exports = async (ctx) => { + const baseUrl = 'https://github.com'; + const { user, repo = 'RSSHub' } = ctx.params; + + // 导入 puppeteer 工具类并初始化浏览器实例 + const browser = await require('@/utils/puppeteer')(); + // 打开一个新标签页 + const page = await browser.newPage(); + // 拦截所有请求 + await page.setRequestInterception(true); + // 仅允许某些类型的请求 + page.on('request', (request) => { + // 在这次例子,我们只允许 HTML 请求 + request.resourceType() === 'document' ? request.continue() : request.abort(); + }); + // 访问目标链接 + const link = `${baseUrl}/${user}/${repo}/issues`; + // got 请求会被自动记录, + // 但 puppeteer 请求不会 + // 所以我们需要手动记录它们 + logger.debug(`Requesting ${link}`); + await page.goto(link, { + // 指定页面等待载入的时间 + waitUntil: 'domcontentloaded', + }); + // 获取页面的 HTML 内容 + const response = await page.content(); + // 关闭标签页 + page.close(); + + const $ = cheerio.load(response); + + // const item = ...; + + // 不要忘记关闭浏览器实例 + browser.close(); + + ctx.state.data = { + // 在此处输出您的 RSS + }; +} +``` + + + + +```js{9} +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + const baseUrl = 'https://github.com'; + const { user, repo = 'RSSHub' } = ctx.params; + + const { data: response } = await got(`${baseUrl}/${user}/${repo}/issues`); + const $ = cheerio.load(response); + + ctx.state.data = { + // 在此处输出您的 RSS + }; +} +``` + + + + +### 获取完整文章 + +使用浏览器新标签页获取每个 GitHub Issue 的正文,类似于 [上一节](#tong-guo-got-cong-html-huo-qu-shu-ju-geng-hao-de-yue-du-ti-yan)。我们可以使用以下代码: + +```js{46-60,71-72} +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); +const logger = require('@/utils/logger'); + +module.exports = async (ctx) => { + const baseUrl = 'https://github.com'; + const { user, repo = 'RSSHub' } = ctx.params; + + const browser = await require('@/utils/puppeteer')(); + const page = await browser.newPage(); + await page.setRequestInterception(true); + page.on('request', (request) => { + request.resourceType() === 'document' ? request.continue() : request.abort(); + }); + + const link = `${baseUrl}/${user}/${repo}/issues`; + logger.debug(`Requesting ${link}`); + await page.goto(link, { + waitUntil: 'domcontentloaded', + }); + const response = await page.content(); + page.close(); + + const $ = cheerio.load(response); + + const list = $('div.js-navigation-container .flex-auto') + .toArray() + .map((item) => { + item = $(item); + const a = item.find('a').first(); + return { + title: a.text(), + link: `${baseUrl}${a.attr('href')}`, + pubDate: parseDate(item.find('relative-time').attr('datetime')), + author: item.find('.opened-by a').text(), + category: item + .find('a[id^=label]') + .toArray() + .map((item) => $(item).text()), + }; + }); + + const items = await Promise.all( + list.map((item) => + ctx.cache.tryGet(item.link, async () => { + // 重用浏览器实例并打开新标签页 + const page = await browser.newPage(); + // 设置请求拦截,仅允许 HTML 请求 + await page.setRequestInterception(true); + page.on('request', (request) => { + request.resourceType() === 'document' ? request.continue() : request.abort(); + }); + + logger.debug(`Requesting ${item.link}`); + await page.goto(item.link, { + waitUntil: 'domcontentloaded', + }); + const response = await page.content(); + // 获取 HTML 内容后关闭标签页 + page.close(); + + const $ = cheerio.load(response); + + item.description = $('.comment-body').first().html(); + + return item; + }) + ) + ); + + // 所有请求完成后关闭浏览器实例 + browser.close(); + + ctx.state.data = { + title: `${user}/${repo} issues`, + link: `https://github.com/${user}/${repo}/issues`, + item: items, + }; +}; +``` + +### 额外资源 + +这里有一些您可以使用的资源来了解 puppeteer: + +- [puppeteer's 旧版文档](https://github.com/puppeteer/puppeteer/blob/v15.2.0/docs/api.md) +- [puppeteer's 当前文档](https://pptr.dev) +- [puppeteer's 非官方中文文档](https://zhaoqize.github.io/puppeteer-api-zh_CN/) + +#### 拦截请求 + +在爬取网页时,您可能会遇到您不需要的图像、字体和其他资源。这些资源会减慢页面加载速度并消耗宝贵的 CPU 和内存资源。为了避免这种情况,您可以在 puppeteer 中启用请求拦截。 + +这是如何实现的: + +```js +await page.setRequestInterception(true); +page.on('request', (request) => { + request.resourceType() === 'document' ? request.continue() : request.abort(); +}); +// 这两条语句必须放在 page.goto() 之前 +``` + +您可以在 [这里](https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-ResourceType) 找到 `request.resourceType()` 的所有可能值。在代码中使用这些值时,请确保使用**小写**字母。 + +#### Wait Until + +在上面的代码中,您将看到在 `page.goto()` 函数中使用了 `waitUntil: 'domcontentloaded'`。这是 puppeteer 的一个选项,它告诉它在何时认为导航成功。您可以在 [这里](https://pptr.dev/api/puppeteer.page.goto/#remarks) 找到所有可能的值及其含义。 + +需要注意的是,`domcontentloaded` 的等待时间较短,而 `networkidle0` 可能不适用于始终发送后台遥测或获取数据的网站。 + +此外,重要的是避免等待特定的超时时间,而是等待选择器出现。等待超时是不准确的,因为它取决于 puppeteer 实例的负载情况。 diff --git a/docs/joinus/new-rss/submit-route.md b/docs/joinus/new-rss/submit-route.md new file mode 100644 index 00000000000000..84c62a2411791b --- /dev/null +++ b/docs/joinus/new-rss/submit-route.md @@ -0,0 +1,122 @@ +--- +sidebarDepth: 2 +--- +# 提交路由 + +当您完成您的路由后,您可以提交一个 Pull Request(下称 PR)到 [RSSHub](https://github.com/DIYgod/RSSHub)。我们将使用 squash merge 策略,这意味着您分支中的所有提交将合并成 RSSHub 仓库上的一个提交。然而,保持您的提交历史干净整洁仍然很重要。我们还提供了一个直观的模板供您填写。 + +## PR 模板 + +````md + + +## 该 PR 相关 Issue / Involved Issue + +Close # + +## 路由地址示例 / Example for the Proposed Route(s) + + + +```routes +``` + +## 新 RSS 路由检查列表 / New RSS Route Checklist + +- [ ] 新的路由 New Route + - [ ] 跟随 [v2 路由规范](https://docs.rsshub.app/joinus/script-standard.html) Follows [v2 Script Standard](https://docs.rsshub.app/en/joinus/script-standard.html) +- [ ] 文档说明 Documentation + - [ ] 中文文档 CN + - [ ] 英文文档 EN +- [ ] 全文获取 fulltext + - [ ] 使用缓存 Use Cache +- [ ] 反爬/频率限制 anti-bot or rate limit? + - [ ] 如果有, 是否有对应的措施? If yes, do your code reflect this sign? +- [ ] [日期和时间](https://docs.rsshub.app/joinus/pub-date.html) [date and time](https://docs.rsshub.app/en/joinus/pub-date.html) + - [ ] 可以解析 Parsed + - [ ] 时区调整 Correct TimeZone +- [ ] 添加了新的包 New package added +- [ ] `Puppeteer` + +## 说明 / Note +```` + +### 相关的 Issue + +您可以在此处填写此 PR 相关的 Issue 编号。如果没有相关的 Issue,请将其留空。如果您的 PR 被合并,相关的 Issue 将自动关闭。如果您想关闭多个 Issue,请添加另一个以空格或逗号分隔的 `Close #`。例如,`Close #123, Close #456, Close #789` 或 `Close #123 Close #456 Close #789`。 + +### 路由地址示例 + +在这里,您可以添加您添加的路由以及所有必需和可选参数。如果您想添加多条路由,请在新行中添加每条路由。例如: + +````md +```routes +/github/issue/DIYgod +/github/issue/DIYgod/RSSHub +/github/issue/DIYgod/RSSHub-Radar +/github/issue/flutter/flutter +``` +```` + +**不要**填写`/github/issue/:user/:repo?` 或 `/issue/:user/:repo?`。 + +如果您的更改与路由无关,例如文档,请在 `routes` 区域中填写 `NOROUTE`。 + +````md +```routes +NOROUTE +``` +```` + +**不要**删除或不理会 `routes` 区域,否则您的 PR 将自动关闭。 + +对于路由相关的 PR,**不要**使用 `NOROUTE`,否则它们也将被自动关闭。 + +### 新 RSS 路由检查表 + +此检查表将帮助您确保您的 PR 包含所有必要的组件。虽然您不必勾选所有项目,以使您的 PR 合并,但请确保您的新路由遵循 [路由规范](/joinus/script-standard.html)。这是所有新路由的强制性要求。 + + +```md +- [ ] 新的路由 New Route +``` + +要勾选项目,请将 `[ ]` 修改为 `[x]`. + +```md +- [x] 新的路由 New Route +``` + +### 说明 + +此部分包含您想要分享的任何附加信息或评论。 + +## PR 标题 + +当您的拉取请求被合并时,拉取请求标题将用作提交信息。请遵循 [约定式提交](https://www.conventionalcommits.org/zh-hans/v1.0.0/#概述) 规范。 + +如果您正在添加新的路由,包括所有必需的文档和 `radar.js`,请以 `route` 作为范围。如果仅添加新的 Radar 规则,则请以 `radar` 作为范围。 + +## 回复代码审查 + +您的拉取请求将由 RSSHub 维护者和机器人审核。您可以点击检查名称旁边的“Details”来检查自动检查的详细信息。如果 RSSHub 维护者要求更改,您可以提交并将更改推送到您的分支。PR 将自动更新以反映您的更改。您也可以使用 [“添加建议到批次 (Add suggestion to batch)”](https://docs.github.com/zh/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/incorporating-feedback-in-your-pull-request#applying-suggested-changes) 批量整合维护者的反馈。 + +## 接下来怎么做 + +当您的 PR 被合并后,将会构建一个新的 Docker 镜像。由于我们需要构建多个平台(包括 `linux/arm/v7`、`linux/arm64` 和 `linux/amd64`)以及包含和不包含 Chromium,该过程可能需要长达一个小时。 diff --git a/docs/joinus/pub-date.md b/docs/joinus/pub-date.md index 4e4476258c84d9..a412778d5502b0 100644 --- a/docs/joinus/pub-date.md +++ b/docs/joinus/pub-date.md @@ -1,55 +1,62 @@ # 日期处理 -在抓取网页的时候,通常情况下网页会提供日期。这篇教程用于说明插件应当如何正确的处理相关情况 +当你访问网站时,网站通常会提供一个日期或时间戳。本指南将展示如何在代码中正确处理它们。 -## 没有日期 +## 规范 -在源没有提供日期的时候,**请勿添加日期**。`pubDate`选项应当被留空。 +### 没有日期 -## 规范 +- 当网站没有提供日期时,**请勿**添加日期,`pubDate` 应当被留空。 +- 当网站提供一个日期但没有准确的时间时,只需要解析日期并**不要添加时间**到 `pubDate` 中。 -`pubDate`必须是一个 +`pubDate` 必须是: -1. [Date Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date)。 -2. **不推荐,用于兼容** 可以被正确解析的字符串。因为其行为可能在不同环境下不一致,[Date.parse()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse),请尽量避免 +1. [Date 对象](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date) +2. **不推荐**: 使用字符串时,要确保可正确解析,因为它们的行为可能会在部署环境中发生不一致。请尽量避免 `Date.parse()`。 -同时,脚本传入的`pubDate`应当是对应**服务器所使用的时区 / 时间**。更多细节参阅下方工具类 +从路由传入的 `pubDate` 应该对应于**服务器使用的时区 / 时间**。有关更多详细信息,请参见下方工具类: ## 使用工具类 -目前,我们推荐使用[dayjs](https://github.com/iamkun/dayjs)进行日期的处理和时区调整。相关工具类有两个: +我们推荐使用 [day.js](https://github.com/iamkun/dayjs) 进行日期处理和时区调整。有两个相关的工具类: -### Parse Date +### 日期时间 -这个是一个工具类用于使用[dayjs](https://github.com/iamkun/dayjs)。大部分情况下,应当可以直接使用他获取到正确的`Date Object` +RSSHub 工具类包括了一个 [day.js](https://github.com/iamkun/dayjs) 的包装函数,它允许你直接解析日期字符串并在大多数情况下获得一个 [Date 对象](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date)。 -具体解析参数请参考 dayjs github 说明 - -```javascript +```js const { parseDate } = require('@/utils/parse-date'); +const pubDate = parseDate('2020/12/30'); +// 或 const pubDate = parseDate('2020/12/30', 'YYYY/MM/DD'); ``` +:::tip 提示 +你可以参考 [day.js 文档](https://day.js.org/docs/zh-CN/parse/string-format#支持的解析占位符列表) 查看所有可用日期格式。 +::: + 如果你需要解析相对日期,请使用 `parseRelativeDate`。 -```javascript +```js const { parseRelativeDate } = require('@/utils/parse-date'); const pubDate = parseRelativeDate('2天前'); const pubDate = parseRelativeDate('前天 15:36'); ``` -### Timezone +### 时区 -部分网站并不会依据访问者来源进行时区转换,此时获取到的时间是网站本地时间,不一定适合所有 RSS 订阅者。此时,应当手动指定获取的时间时区: +从网站解析日期时,考虑时区非常重要。有些网站可能不会根据访问者的位置转换时区,导致日期不准确地反映用户的本地时间。为避免此问题,你可以手动指定时区。 -::: warning 注意 -此时,时间将会被转换到服务器时间,方便后续中间件处理。这个是正常流程! -::: +要在代码中手动指定时区,可以使用以下代码: -```javascript +```js const timezone = require('@/utils/timezone'); -const pubDate = timezone(new Date(), +8) +const pubDate = timezone(parseDate('2020/12/30 13:00'), +1); ``` + +`timezone` 函数接受两个参数:第一个是 [日期对象](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date),第二个是时区偏移量。偏移量以小时为单位指定,在此示例中使用了 UTC+1 的时区。 + +这样做将时间转换为服务器时间,方便后续中间件进行处理。 diff --git a/docs/joinus/quick-start.md b/docs/joinus/quick-start.md index 5532011b9bb985..4423df015db00a 100644 --- a/docs/joinus/quick-start.md +++ b/docs/joinus/quick-start.md @@ -1,610 +1,45 @@ ---- -sidebar: auto ---- - # 参与我们 -如果有任何想法或需求,可以在 [issue](https://github.com/DIYgod/RSSHub/issues) 中告诉我们,同时我们欢迎各种 pull requests +如果您在使用 RSSHub 过程中遇到了问题或者有建议改进,我们很乐意听取您的意见!您可以通过 Pull Request 来提交您的修改。无论您对 Pull Request 的使用是否熟悉,我们都欢迎不同经验水平的开发者参与贡献。如果您不懂编程,也可以通过 [报告错误](https://github.com/DIYgod/RSSHub/issues) 的方式来帮助我们。 ## 参与讨论 -1. [Telegram 群](https://t.me/rsshub) -2. [GitHub Issues](https://github.com/DIYgod/RSSHub/issues) - -## 提交新的 RSSHub 规则 - -开始编写 RSS 源前请确认源站没有提供 RSS,部分网页会在 HTML 头部包含 type 为 `application/atom+xml` 或 `application/rss+xml` 的 link 元素来指明 RSS 链接 - -### 调试 - -首先 `yarn` 或者 `npm install` 安装依赖,然后执行 `yarn dev` 或者 `npm run dev`,打开 `http://localhost:1200` 就可以看到效果,修改文件也会自动刷新 - -### 添加脚本路由 - -在 [/lib/v2/](https://github.com/DIYgod/RSSHub/tree/master/lib/v2) 中创建对应路由路径,并在 `/lib/v2/:path/router.js` 中添加路由 - -### 编写脚本 - -在 [/lib/v2/](https://github.com/DIYgod/RSSHub/tree/master/lib/v2) 中的路由对应路径下创建新的 js 脚本: - -#### 获取源数据 - -- 获取源数据的主要手段为使用 [got](https://github.com/sindresorhus/got) 发起 HTTP 请求(请求接口或请求网页)获取数据 - -- 个别情况需要使用 [puppeteer](https://github.com/GoogleChrome/puppeteer) 模拟浏览器渲染目标页面并获取数据 - -- 返回的数据一般为 JSON 或 HTML 格式 - -- 对于 HTML 格式的数据,使用 [cheerio](https://github.com/cheeriojs/cheerio) 进行处理 - -- 以下三种获取数据方法按 **「推荐优先级」** 排列: - - 1. **使用 got 从接口获取数据** - - 样例:[/lib/routes/bilibili/coin.js](https://github.com/DIYgod/RSSHub/blob/master/lib/routes/bilibili/coin.js)。 - - 使用 got 通过数据源提供的 API 接口获取数据: - - ```js - // 发起 HTTP GET 请求 - const response = await got({ - method: 'get', - url: `https://api.bilibili.com/x/space/coin/video?vmid=${uid}&jsonp=jsonp`, - headers: { - Referer: `https://space.bilibili.com/${uid}/`, - }, - }); - - const data = response.data.data; // response.data 为 HTTP GET 请求返回的数据对象 - // 这个对象中包含了数组名为 data,所以 response.data.data 则为需要的数据 - ``` - - 返回的数据样例之一(response.data.data \[0]): - - ```json - { - "aid": 33614333, - "videos": 2, - "tid": 20, - "tname": "宅舞", - "copyright": 1, - "pic": "http://i0.hdslb.com/bfs/archive/5649d7fe6ff7f7b431300fc1a0db80d3f174cacd.jpg", - "title": "【赤九玖】响喜乱舞【和我一起狂舞吧,团长大人(✧◡✧)】", - "pubdate": 1539259203, - "ctime": 1539249536, - "desc": "编舞出处:av31984673\n真心好喜欢这个舞和这首歌,居然恰巧被邀请跳了,感谢《苍之纪元》官方的邀请。这次cos的是游戏的新角色缪斯。然而时间有限很多地方还有很多不足。也没跳够,以后私下还会继续练习,希望能学到更多动作,也能为了有机会把它跳的更好。 \n摄影:绯山圣瞳九命猫 \n后期:炉火" - // 省略部分数据 - } - ``` - - 对数据进行进一步处理,生成符合 RSS 规范的对象,把获取的标题、链接、描述、发布时间等数据赋值给 ctx.state.data, [生成 RSS 源](#生成-rss-源): - - ```js - ctx.state.data = { - // 源标题 - title: `${name} 的 bilibili 投币视频`, - // 源链接 - link: `https://space.bilibili.com/${uid}`, - // 源说明 - description: `${name} 的 bilibili 投币视频`, - //遍历此前获取的数据 - item: data.map((item) => ({ - // 文章标题 - title: item.title, - // 文章正文 - description: `${item.desc}
`, - // 文章发布时间 - pubDate: new Date(item.time * 1000).toUTCString(), - // 文章链接 - link: `https://www.bilibili.com/video/av${item.aid}`, - })), - }; - - // 至此本路由结束 - ``` - - 2. **使用 got 从 HTML 获取数据** - - 有时候数据是写在 HTML 里的,**没有接口供我们调用**,样例: [/lib/routes/douban/explore.js](https://github.com/DIYgod/RSSHub/blob/master/lib/routes/douban/explore.js)。 - - 使用 got 请求 HTML 数据: - - ```js - // 发起 HTTP GET 请求 - const response = await got({ - method: 'get', - url: 'https://www.douban.com/explore', - }); - - const data = response.data; // response.data 为 HTTP GET 请求返回的 HTML,也就是简书首页的所有 HTML - ``` - - 使用 cheerio 解析返回的 HTML: - - ```js - const $ = cheerio.load(data); // 使用 cheerio 加载返回的 HTML - const list = $('div[data-item_id]'); - // 使用 cheerio 选择器,选择带有 data-item_id 属性的所有 div 元素,返回 cheerio node 对象数组 - - // 注:每一个 cheerio node 对应一个 HTML DOM - // 注:cheerio 选择器与 jquery 选择器几乎相同 - // 参考 cheerio 文档:https://cheerio.js.org/ - ``` - - 使用 map 遍历数组,解析出每一个 item 的结果 - - ```js - ctx.state.data = { - title: '豆瓣-浏览发现', - link: 'https://www.douban.com/explore', - item: - list && - list - .map((index, item) => { - item = $(item); - itemPicUrl = item.find('a.cover').attr('style').replace('background-image:url(', '').replace(')', ''); - return { - title: item.find('.title a').first().text(), - description: `作者:${item.find('.usr-pic a').last().text()}
描述:${item.find('.content p').text()}
`, - link: item.find('.title a').attr('href'), - }; - }) - .get(), - }; - - // 至此本路由结束 - ``` - - 3. **使用 puppeteer 渲染页面获取数据** - - ::: tip 提示 - - 由于此方法性能较差且消耗较多资源,使用前请确保以上两种方法无法获取数据,不然将导致您的 pull requests 被拒绝! - - ::: - - 部分网站**没有接口供调用,且页面有加密** - 样例:[/lib/routes/sspai/series.js](https://github.com/DIYgod/RSSHub/blob/master/lib/routes/sspai/series.js) - - ```js - // 使用 RSSHub 提供的 puppeteer 工具类,初始化 Chrome 进程 - const browser = await require('@/utils/puppeteer')(); - // 创建一个新的浏览器页面 - const page = await browser.newPage(); - // 访问指定的链接 - const link = 'https://sspai.com/series'; - await page.goto(link); - // 渲染目标网页 - const html = await page.evaluate( - () => - // 选取渲染后的 HTML - document.querySelector('div.new-series-wrapper').innerHTML - ); - // 关闭浏览器进程 - browser.close(); - ``` - - 使用 cheerio 解析返回的 HTML: - - ```js - const $ = cheerio.load(html); // 使用 cheerio 加载返回的 HTML - const list = $('div.item'); // 使用 cheerio 选择器,选择所有
元素,返回 cheerio node 对象数组 - ``` - - 赋值给 `ctx.state.data` - - ```js - ctx.state.data = { - title: '少数派 -- 最新上架付费专栏', - link, - description: '少数派 -- 最新上架付费专栏', - item: list - .map((i, item) => ({ - // 文章标题 - title: $(item).find('.item-title a').text().trim(), - // 文章链接 - link: url.resolve(link, $(item).find('.item-title a').attr('href')), - // 文章作者 - author: $(item).find('.item-author').text().trim(), - })) - .get(), // cheerio get() 方法将 cheerio node 对象数组转换为 node 对象数组 - }; - - // 至此本路由结束 - - // 注:由于此路由只是起到一个新专栏上架提醒的作用,无法访问付费文章,因此没有文章正文 - ``` - - 4. **使用通用配置型路由** - - 很大一部分网站是可以通过一个配置范式来生成 RSS 的。 - 通用配置即通过 cheerio(**CSS 选择器、jQuery 函数**)读取 json 数据来简便的生成 RSS。 - - 首先我们需要几个数据: - - 1. RSS 来源链接 - 2. 数据来源链接 - 3. RSS 标题(非 item 标题) - - ```js - const buildData = require('@/utils/common-config'); - module.exports = async (ctx) => { - ctx.state.data = await buildData({ - link: '', // RSS来源链接 - url: '', // 数据来源链接 - title: '%title%', // 这里使用了变量,形如 **%xxx%** 这样的会被解析为变量,值为 **params** 下的同名值 - params: { - title: '', // RSS标题 - }, - }); - }; - ``` - - 至此,我们的 RSS 还没有任何内容,内容需要由`item`完成 - 下面为一个实例 - - ```js - const buildData = require('@/utils/common-config'); - - module.exports = async (ctx) => { - const link = `https://www.uraaka-joshi.com/`; - ctx.state.data = await buildData({ - link, - url: link, - title: `%title%`, - params: { - title: '裏垢女子まとめ', - }, - item: { - item: '.content-main .stream .stream-item', - title: `$('.post-account-group').text() + ' - %title%'`, // 只支持$().xxx()这样的js语句,也足够使用 - link: `$('.post-account-group').attr('href')`, // .text()代表获取元素的文本,.attr()表示获取指定属性 - description: `$('.post .context').html()`, // .html()代表获取元素的html代码 - pubDate: `new Date($('.post-time').attr('datetime')).toUTCString()`, // 日期的格式多种多样,可以尝试使用**/utils/date** - guid: `new Date($('.post-time').attr('datetime')).getTime()`, // guid必须唯一,这是RSS的不同item的标志 - }, - }); - }; - ``` - - 至此我们完成了一个最简单的路由 - -* * * - -#### 使用缓存 - -所有路由都有一个缓存,全局缓存时间在 `lib/config.js` 里设定,但某些接口返回的内容更新频率较低,这时应该给这些数据设置一个更长的缓存时间,比如需要额外请求的全文内容 - -例如 bilibili 专栏 需要获取文章全文:[/lib/routes/bilibili/followings_article.js](https://github.com/DIYgod/RSSHub/blob/master/lib/routes/bilibili/followings_article.js) - -由于无法从一个接口获取所有文章的全文,所以每篇文章都需要单独请求一次,而这些数据一般是不变的,应该把这些数据保存到缓存里,避免每次访问路由都去请求那么多接口 - -```js -const description = await ctx.cache.tryGet(link, async () => { - const result = await got.get(link); - - const $ = cheerio.load(result.data); - $('img').each(function (i, e) { - $(e).attr('src', $(e).attr('data-src')); - }); - - return $('.article-holder').html(); -}); -``` - -tryGet 的实现可以看[这里](https://github.com/DIYgod/RSSHub/blob/master/lib/middleware/cache/index.js#L58)。第一个参数为缓存的 key;第二个参数为缓存未命中时的数据获取方法;第三个参数为缓存时间,正常情况不应该传入,缓存时间默认为 [CACHE_CONTENT_EXPIRE](/install/#缓存配置);第四个参数为控制本次尝试缓存命中时是否需要重新计算过期时间(给缓存「续期」)的开关,`true` 为打开,`false` 为关闭,默认为打开 +[![Telegram 群组](https://img.shields.io/badge/chat-telegram-brightgreen.svg?logo=telegram\&style=for-the-badge)](https://t.me/rsshub) [![GitHub Issues](https://img.shields.io/github/issues/DIYgod/RSSHub?color=bright-green\&logo=github\&style=for-the-badge)](https://github.com/DIYgod/RSSHub/issues) [![GitHub 讨论](https://img.shields.io/github/discussions/DIYgod/RSSHub?logo=github\&style=for-the-badge)](https://github.com/DIYgod/RSSHub/discussions) -* * * +## 开始之前 -#### 生成 RSS 源 +要制作一个 RSS 订阅,您需要结合使用 Git、HTML、JavaScript、jQuery 和 Node.js。 -获取到的数据赋给 ctx.state.data, 然后数据会经过 [template.js](https://github.com/DIYgod/RSSHub/blob/master/lib/middleware/template.js) 中间件处理,最后传到 [/lib/views/rss.art](https://github.com/DIYgod/RSSHub/blob/master/lib/views/rss.art) 来生成最后的 RSS 结果,每个字段的含义如下: +如果您对它们不是很了解,但想要学习它们,以下是一些好的资源: -```js -ctx.state.data = { - title: '', // 项目的标题 - link: '', // 指向项目的链接 - description: '', // 描述项目 - language: '', // 频道语言 - allowEmpty: false, // 默认 false,设为 true 可以允许 item 为空 - item: [ - // 其中一篇文章或一项内容 - { - title: '', // 文章标题 - author: '', // 文章作者 - category: '', // 文章分类 - // category: [''], // 多个分类 - description: '', // 文章摘要或全文 - pubDate: '', // 文章发布时间 - guid: '', // 文章唯一标示, 必须唯一, 可选, 默认为文章链接 - link: '', // 指向文章的链接 - }, - ], -}; -``` +- [MDN Web Docs 上的 JavaScript 指南](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript#教程) +- [W3Schools](https://www.w3schools.com) +- [Codecademy 上的 Git 课程](https://www.codecademy.com/learn/learn-git) -::: warning 注意 +如果您想查看其他开发人员如何使用这些技术来制作 RSS 订阅的示例,您可以查看 [我们的代码库](https://github.com/DIYgod/RSSHub/tree/master/lib/v2) 中的一些代码。 -`title`, `subtitle` (仅适用于 atom 输出), `author` (仅适用于 atom 输出), `item.title`, `item.author` 不应该包含换行、多于一个的连续空字符,或以空字符开头 / 结尾。\ -多数 RSS 阅读器会自动为上述字段修剪空字符,所以这些空字符没有意义。但是,某些阅读器也许不能正确处理它们,因此,我们会在最终输出前修剪上述字段,确保不含有换行或多于一个空字符,也不以空字符开头或结尾。\ -如果你要编写的路由在上述字段不能容忍空字符修剪,你应该考虑变换一下这些字段的格式。 - -另外,虽然其它字段不会经过强制空字符修剪,但你也应该尽量避免违反上述规则。尤其是使用 cheerio 提取网页元素或文本时,需要时刻谨记 cheerio 会保留换行和缩进。特别地,对于 `item.description` ,任何预期之内的换行都应被转换为 `
` ,否则 RSS 阅读器很可能将它修剪;尤其如果你从 JSON 提取 RSS 源,目标网站返回的 JSON 很有可能含有需要显示的换行,这时候就一定要进行转换。 - -::: - -##### 播客源 - -用于音频类 RSS,**额外**添加这些字段能使你的 RSS 被泛用型播客软件订阅: - -```js -ctx.state.data = { - itunes_author: '', // 主播名字, 必须填充本字段才会被视为播客 - itunes_category: '', // 播客分类 - image: '', // 专辑图片, 作为播客源时必填 - item: [ - { - itunes_item_image: '', // 每个track单独的图片 - itunes_duration: '', // 音频长度,总共的秒数或者 H:mm:ss,可选 - enclosure_url: '', // 音频链接 - enclosure_length: '', // 文件大小 (单位: Byte),可选 - enclosure_type: '', // [.mp3就填'audio/mpeg'] [.m4a就填'audio/x-m4a'] [.mp4就填'video/mp4'], 或其他类型. - }, - ], -}; -``` - -##### BT / 磁力源 - -用于下载类 RSS,**额外**添加这些字段能使你的 RSS 被 BT 客户端识别并自动下载: - -```js -ctx.state.data = { - item: [ - { - enclosure_url: '', // 磁力链接 - enclosure_length: '', // 文件大小 (单位: Byte),可选 - enclosure_type: 'application/x-bittorrent', // 固定为 'application/x-bittorrent' - }, - ], -}; -``` - -##### 媒体源 - -**额外**添加这些字段能使你的 RSS 被支持 [Media RSS](http://www.rssboard.org/media-rss) 的软件订阅: - -示例: - -```js -ctx.state.data = { - item: [ - { - media: { - content: { - url: post.file_url, - type: `image/${mime[post.file_ext]}`, - }, - thumbnail: { - url: post.preview_url, - }, - }, - }, - ], -}; -``` - -##### 互动 - -**额外**添加这些字段能使你的 RSS 被支持的软件订阅: - -```js -ctx.state.data = { - item: [ - { - upvotes: 0, // 默认为空,文章有多少 upvote - downvotes: 0, // 默认为空,文章有多少 downvote - comments: 0, // 默认为空,文章有多少评论 - }, - ], -}; -``` - -* * * - -### 添加脚本文档 - -1. 更新 [文档 (/docs/) ](https://github.com/DIYgod/RSSHub/blob/master/docs/) 目录内对应的文档,可以执行 `npm run docs:dev` 查看文档效果 - - - 文档采用 vue 组件形式,格式如下: - - `author`: 路由作者,多位作者使用单个空格分隔 - - `example`: 路由举例 - - `path`: 路由路径 - - `:paramsDesc`: 路由参数说明,数组,支持 markdown - 1. 参数说明必须对应其在路径中出现的顺序 - 2. 如缺少说明将会导致`npm run docs:dev`报错 - 3. 说明中的 `'` `"` 必须通过反斜杠转义 `\'` `\"` - 4. 路由参数以 `?`、`*`、`+`、普通字符结尾分别表示 “可选”、“零个或多个”、“单个或多个”、“必选”,由组件自动判断,不必在说明中标注 - - 文档样例: - - 1. 无参数: - - ```vue - - ``` - - 结果预览: - - * * * - - - - * * * - - 2. 多参数: - - ```vue - - ``` - - 结果预览: - - * * * - - - - * * * - - 3. 复杂说明支持 slot: - - ```vue - - - | 前端 | Android | iOS | 后端 | 设计 | 产品 | 工具资源 | 阅读 | 人工智能 | - | -------- | ------- | --- | ------- | ------ | ------- | -------- | ------- | -------- | - | frontend | android | ios | backend | design | product | freebie | article | ai | - - - ``` - - 结果预览: - - * * * +## 提交新的 RSSHub 规则 - +如果您发现一个网站没有提供 RSS 订阅,您可以使用 RSSHub 制作一个 RSS 规则。RSS 规则是一个短小的 Node.js 程序代码(以下简称 “路由”),它告诉 RSSHub 如何从网站中提取内容并生成 RSS 订阅。通过制作新的 RSS 路由,您可以帮助让您喜爱的网站的内容被更容易访问和关注。 - | 前端 | Android | iOS | 后端 | 设计 | 产品 | 工具资源 | 阅读 | 人工智能 | - | -------- | ------- | --- | ------- | ------ | ------- | -------- | ------- | -------- | - | frontend | android | ios | backend | design | product | freebie | article | ai | +在您开始编写 RSS 路由之前,请确保源站点没有提供 RSS。一些网页会在 HTML 头部中包含一个 type 为 `application/atom+xml` 或 `application/rss+xml` 的 link 元素来指示 RSS 链接。 - +这是在 HTML 头部中看到 RSS 链接可能会长成这样:``。如果您看到这样的链接,这意味着这个网站已经有了一个 RSS 订阅,您不需要为它制作一个新的 RSS 路由。 - * * * +### 开始 -2. 请一定要注意把``的标签关闭! +在本指南中,您将学习从头制作一个新的 RSS 路由的方法。我们将涵盖从设置开发环境到提交代码到 RSSHub 仓库的所有内容。到本指南结束时,您将能够为不提供 RSS 的网站制作自己的 RSS 订阅源。 -3. 执行 `npm run format` 自动标准化代码格式,提交代码,然后提交 pull request +[准备好了吗?点这里开始学习!](/joinus/new-rss/prerequisites.html) ## 提交新的 RSSHub Radar 规则 -### 调试 - -打开浏览器扩展设置页,切换到规则列表页,下拉页面可以看到一个文本框,把新规则复制到文本框里就可以用来调试 - -### 编写规则 - -在 [/lib/v2/](https://github.com/DIYgod/RSSHub/tree/master/lib/v2) 的对应路由下创建 `radar.js` 并添加规则 - -下面说明中会用到的简化的规则: - -```js -{ - 'bilibili.com': { - _name: 'bilibili', - www: [{ - title: '分区视频', - docs: 'https://docs.rsshub.app/social-media.html#bilibili', - source: '/v/*tpath', - target: (params) => { - let tid; - switch (params.tpath) { - case 'douga/mad': - tid = '24'; - break; - default: - return false; - } - return `/bilibili/partion/${tid}`; - }, - }], - }, - 'twitter.com': { - _name: 'Twitter', - '.': [{ // for twitter.com - title: '用户时间线', - docs: 'https://docs.rsshub.app/social-media.html#twitter', - source: '/:id', - target: (params) => { - if (params.id !== 'home') { - return '/twitter/user/:id'; - } - }, - }], - }, - 'pixiv.net': { - _name: 'Pixiv', - 'www': [{ - title: '用户收藏', - docs: 'https://docs.rsshub.app/social-media.html#pixiv', - source: '/bookmark.php', - target: (params, url) => `/pixiv/user/bookmarks/${new URL(url).searchParams.get('id')}`, - }], - }, - 'weibo.com': { - _name: '微博', - '.': [{ - title: '博主', - docs: 'https://docs.rsshub.app/social-media.html#%E5%BE%AE%E5%8D%9A', - source: ['/u/:id', '/:id'], - target: (params, url, document) => { - const uid = document && document.documentElement.innerHTML.match(/\$CONFIG\['oid']='(\d+)'/)[1]; - return uid ? `/weibo/user/${uid}` : ''; - }, - }], - }, -} -``` - -下面详细说明这些字段的含义及用法 - -#### title - -必填,路由名称 - -对应 RSSHub 文档中的名称,如 `Twitter 用户时间线` 规则的 `title` 为 `用户时间线` - -#### docs - -必填,文档地址 - -如 `Twitter 用户时间线` 规则的 `docs` 为 `https://docs.rsshub.app/social-media.html#twitter` - -注意不是 `https://docs.rsshub.app/social-media.html#yong-hu-shi-jian-xian`,hash 应该定位到一级标题 - -#### source - -可选,源站路径,留空则永远不会匹配成功,只会在 `适用于当前网站的 RSSHub` 中出现 - -如 `Twitter 用户时间线` 规则的 `source` 为 `/:id` - -比如我们现在在 `https://twitter.com/DIYgod` 这个页面,`twitter.com/:id` 匹配成功,结果 params 为 `{id: 'DIYgod'}`,下一步中插件就会根据 params `target` 字段生成 RSSHub 地址 - -请注意 `source` 只可以匹配 URL Path,如果参数在 URL Param 和 URL Hash 里请使用 `target` - -#### target - -可选,RSSHub 路径,留空则不会生成 RSSHub 路径 - -对应 RSSHub 文档中的 path,如 `Twitter 用户时间线` 规则的 `target` 为 `/twitter/user/:id` - -上一步中源站路径匹配出 `id` 为 `DIYgod`,则 RSSHub 路径中的 `:id` 会被替换成 `DIYgod`,匹配结果为 `/twitter/user/DIYgod`,就是我们想要的结果 - -进一步,如果源站路径无法匹配出想要的参数,这时我们可以把 `target` 设为一个函数,函数有 `params` 、 `url` 和 `document` 三个参数 - -`params` 为上一步 `source` 匹配出来的参数,`url` 为页面 url,`document` 为页面 document - -请注意,`target` 方法运行在沙盒中,对 `document` 的任何修改都不会反应到页面中 - -### RSSBud - -[RSSBud](https://github.com/Cay-Zhang/RSSBud) 支持 RSSHub Radar 的规则并且也会自动更新,但是请注意: - -- 在 Radar 的规则中使用 `'.'` 子域名可以让 RSSBud 适配 `m` / `mobile` 等常见移动端子域名 +### 开始之前 -- 在 `target` 中使用 `document` 的规则并不适用 RSSBud:RSSBud 并不是一个浏览器插件,他只获取并分析网站的 URL +建议您在开始之前,在浏览器中下载并安装 RSSHub Radar。 -### 补充文档 +安装 RSSHub Radar 后,打开设置并切换到 “规则列表” 选项页。然后滚动到页面底部,您会看到一个文本框。在这里,您可以使用您的新规则替换旧规则以进行调试。 -在 RSSHub 文档里给对应路径加上 `radar="1"`,这样就会显示一个 `支持浏览器扩展` 标记 +[开始吧!](/joinus/new-radar.html) -如果也支持 RSSBud,再加上 `rssbud="1"`,会显示 `支持 RSSBud` 标记 +为 Chromium 安装 RSSHub Radar 为 Firefox 安装 RSSHub Radar for 为 Edge 安装 RSSHub Radar 为 Safari 安装 RSSHub Radar diff --git a/docs/joinus/script-standard.md b/docs/joinus/script-standard.md index 9fa2c698db886e..d2aef959662dc5 100644 --- a/docs/joinus/script-standard.md +++ b/docs/joinus/script-standard.md @@ -1,19 +1,79 @@ +--- +sidebarDepth: 2 +--- + # 路由规范 -::: warning 警告 +## 代码规范 -这个规范仍在制定过程中,可能会随着时间推移而发生改变,请记得多回来看看! +### 通用准则 -::: +- **保持一致!** +- 避免使用已经被废弃的特性。 +- 避免修改 `yarn.lock` 和 `package.json`,除非您添加了新的依赖。 +- 将重复的代码合并为函数。 +- 优先使用更高版本的 ECMAScript 标准特性,而不是使用低版本特性。 +- 按字母顺序排序(大写字母优先),以便更容易找到条目。 +- 尽量使用 HTTPS 而非 HTTP 传输数据。 +- 尽量使用 WebP 格式而非 JPG 格式,因为前者支持更好的压缩。 + +### 代码格式 + +#### 缩进 + +- 使用 4 个空格缩进。 + +#### 分号 + +- 在每条语句结尾添加分号。 + +#### 字符串 + +- 使用单引号而不是双引号。 +- 使用 [模板字符串](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Template_literals) 而非复杂的字符串拼接。 +- 对于 GraphQL 查询,使用 [模板字符串](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Template_literals)。 + +#### 空格 + +- 在每个文件末尾添加一个空行。 +- 避免尾随空格,代码应整洁易读。 + +### 语言特性 + +#### 类型转换 + +- 避免重复转换同一类型。 + +#### 函数 + +- 优先使用 [箭头函数](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions),而不是使用 `function` 关键字定义函数。 + +#### 循环 + +- 对于数组,使用 `for-of`,而不是使用 `for`。([javascript:S4138](https://rules.sonarsource.com/javascript/RSPEC-4138)) + +#### 变量 + +- 使用 `const` 和 `let` 而不是 `var`。 +- 每次声明一个变量。 -在编写新的路由时,RSSHub 会读取文件夹中的: +### 命名 -- `router.js`注册路由 -- `maintainer.js`获取路由路径,维护者 -- `radar.js`获取路由所对应的网站,以及匹配规则: -- `templates` 渲染模版 +- 使用 `lowerCamelCase` 命名变量和函数 +- 使用 `kebab-case` 命名文件和文件夹,也可以使用 `snake_case`。 +- 使用 `CONSTANT_CASE` 命名常量。 -**以上文件为所有插件必备** +## v2 路由规范 + +When creating a new route in RSSHub, you need to organize your files in a specific way. Your namespace folder should be stored in the `lib/v2` directory and should include three mandatory files: + +当在 RSSHub 中编写新的路由时,需要按特定方式组织文件。命名空间文件夹应该存储在 `lib/v2` 目录下,并且应包括三个必需文件: + +- `router.js` 注册路由 +- `maintainer.js` 提供路由维护者信息 +- `radar.js` 为每个路由提供对应 [RSSHub Radar](https://github.com/DIYgod/RSSHub-Radar) 规则 + +命名空间文件夹结构应该像这样: ├───lib/v2 │ ├───furstar @@ -24,122 +84,67 @@ │ ├─── radar.js │ ├─── someOtherJs.js │ └───test - │ └───someOtherScript + │ └───someOtherNamespaces ... -**所有符合条件的,在`/v2`路径下的路由,将会被自动载入,无需更新`router.js`** - -## 路由示例 +**所有符合条件的,在 `lib/v2` 路径下的路由将会被自动载入,无需更新 `lib/router.js`** -参考`furstar`: `./lib/v2/furstar` +### 命名空间 -可以复制该文件夹作为新路由模版 +RSSHub 会将所有路由命名空间的文件夹名附加到路由前面。路由维护者可命名空间视为根目录。 -## 注册路由 +#### 命名规范 -`router.js` 应当导出一个方法,我们在初始化路由的时候,会提供一个`@koa/router`对象 - -### 命名规范 - -我们会默认将所有的路由文件夹名字附加在真正的路由前面。路由维护者可以认定自己获取的就是根,我们会在附加对应的命名空间,在这空间底下,开发者有所有的控制权 - -### 例子 - -```js -module.exports = function (router) { - router.get('/characters/:lang?', require('./index')); - router.get('/artists/:lang?', require('./artists')); - router.get('/archive/:lang?', require('./archive')); -}; -``` +- 使用二级域名 (second-level domain, SLD) 作为命名空间。有关 URL 结构的更多信息,请参阅 [此页面](/joinus/new-radar.html#ding-ceng-dui-xiang-jian)。 +- 不要创建相同命名空间的变体。有关更多信息,请参阅 [此页面](/joinus/new-rss/before-start.html#chuang-jian-ming-ming-kong-jian) -## 维护者列表 +### 注册路由 -`maintainer.js` 应当导出一个对象,在我们获取路径相关信息时,将会在从这里调取开发者信息等 +`router.js` 文件应导出一个方法,提供在初始化路由时使用的 `@koa/router` 对象。 -- key: `@koa/router` 对应的路径匹配 -- value: 数组,包含所有开发者的 Github Username +### 维护者列表 -Github ID 可能是更好的选择,但是后续处理不便,目前暂定 Username +`maintainer.js` 文件应导出一个对象,提供与路由相关的维护者信息,包括: -### 例子 +- 键: `@koa/router` 对象中对应的路由 +- 值:一个字符串数组,包括所有维护者的 GitHub ID。 -```js -module.exports = { - '/characters/:lang?': ['NeverBehave'], - '/artists/:lang?': ['NeverBehave'], - '/archive/:lang?': ['NeverBehave'], -}; -``` +要生成维护者列表,可使用以下命令:`yarn build:maintainer`,它将在 `assets/build/` 目录下一份维护者列表。 -`npm run build:maintainer` 将会在`assets/build`下生成一份贡献者清单 +::: danger 警告 +The path in the `@koa/router` object should be the same as the `path` in the corresponding documentation with the namespace appended in front of it. -## Radar Rules +在 `@koa/router` 对象中的路由应该与相应的文档中添加命名空间前的 `path` 一致。 +::: -书写方式: +### Radar 规则 -**我们目前要求所有路由,必须包含这个文件,并且包含对应的域名 -- 我们不要求完全的路由匹配,最低要求是在对应的网站,可以显示支持即可。这个文件后续会用于帮助 bug 反馈。** +所有路由都需要包含 `radar.js` 文件,其中包括相应的域名。最低要求是规则出现在相应的站点上,即需要填写 `title` 和 `docs` 字段。 -### 例子 +要生成完整的 `radar-rules.js` 文件,可使用以下命令:`yarn build:radar`,它将在 `assets/build/` 目录下创建文件。 -```js -module.exports = { - 'furstar.jp': { - _name: 'Furstar', - '.': [ - { - title: '最新售卖角色列表', - docs: 'https://docs.rsshub.app/shopping.html#furstar-zui-xin-shou-mai-jiao-se-lie-biao', - source: ['/:lang', '/'], - target: '/furstar/characters/:lang', - }, - { - title: '已经出售的角色列表', - docs: 'https://docs.rsshub.app/shopping.html#furstar-yi-jing-chu-shou-de-jiao-se-lie-biao', - source: ['/:lang/archive.php', '/archive.php'], - target: '/furstar/archive/:lang', - }, - { - title: '画师列表', - docs: 'https://docs.rsshub.app/shopping.html#furstar-hua-shi-lie-biao', - source: ['/'], - target: '/furstar/artists', - }, - ], - }, -}; -``` +::: tip 提示 +在提交代码之前,请记得删除所有在 `assets/build/` 中的生成的资源。 +::: -`npm run build:radar` 将会在`/assets/build/`下生成一份完整的`radar-rules.js` +### 渲染模板 -## Template +当渲染自定义 HTML 内容(例如 `item.description`)时,**必须**使用 [art-template](https://aui.github.io/art-template/) 进行排版。 -我们目前要求所有路由,在渲染`description`等带 HTML 的内容时,**必须**使用 art 引擎进行排版 +所有模板都应放置在路由命名空间下的 `templates` 文件夹中,并使用 `.art` 文件扩展名命名。 -art 说明文档: +#### 示例 -同时,所有模版应该放在插件`templates`文件夹中 -- 后续我们会以此提供自定义模版切换 / 渲染等需求 +下面是在 [furstar](https://github.com/DIYgod/RSSHub/blob/master/lib/v2/furstar) 命名空间中示例: -### 例子 +<<< @/lib/v2/furstar/templates/author.art -```art -
- - {{ if link !== null }} - {{name}} - {{ else }} - {{name}} - {{ /if }} -
-``` + ```js +const path = require('path'); const { art } = require('@/utils/render'); const renderAuthor = (author) => art(path.join(__dirname, 'templates/author.art'), author); ``` -## ctx.state.json - -插件目前可以提供一个自定义的对象,用于调试 -- 访问对应路由 +`.debug.json`即可获取到对应内容 - -我们对这个部分格式内容没有任何限制,完全可选,目前会继续观察这个选项的发展方向 + diff --git a/docs/joinus/use-cache.md b/docs/joinus/use-cache.md index b46d493ce3fd14..bb823aa5419828 100644 --- a/docs/joinus/use-cache.md +++ b/docs/joinus/use-cache.md @@ -1,5 +1,84 @@ # 使用缓存 -部分 RSS 在生成时需要访问数个页面,这些页面同时并不是很可能经常变化。出于减轻对方服务器压力和节约不必要流量 / 算力的考虑,这种情况下应当使用缓存。下面是关于缓存工具类的使用场景和具体介绍 +所有路由都有一个缓存,该缓存在短时间后过期。您可以通过环境变量来修改 `lib/config.js` 文件中的 `CACHE_EXPIRE` 值使用来更改缓存的持续时间。然而,对于那些内容更新较少的接口,最好是使用 `CACHE_CONTENT_EXPIRE` 来指定较长的缓存过期时间。 - +例如,为了获取每个 GitHub Issue 的第一个评论的正文,您可以向 `${baseUrl}/${user}/${repo}/issues/${id}` 发出请求,因为 `${baseUrl}/${user}/${repo}/issues` 无法提供此数据。推荐将此数据存储在缓存中,以避免重复向服务器发出请求。 + +以下是如何使用缓存获取数据的示例代码: + +```js + const items = await Promise.all( + list.map((item) => + ctx.cache.tryGet(item.link, async () => { + const { data: response } = await got(item.link); + const $ = cheerio.load(response); + + item.description = $('.comment-body').first().html(); + + return item; + }) + ) + ); +``` + +以上代码片段来自 [制作自己的 RSSHub 路由](/joinus/new-rss/start-code.html),展示了如何使用缓存获取每个问题的第一个评论的全文。使用 `ctx.cache.tryGet()` 来确定数据是否已经在缓存中。如果不在,则代码会获取数据并将其存储在缓存中。 + +上一个语句返回的对象将被重复使用,并且会添加一个额外的 `description` 属性。每个 `item.link` 的返回缓存将是`{ title, link, pubDate, author, category, description }`。下一次请求相同路由时,将直接返回处理过后的缓存而不是向服务器发出请求并重新计算数据。 + +::: warning 注意 +在 `tryGet()` 函数之外声明的变量的任何赋值都不会在缓存命中的情况下被处理。例如,以下代码将无法按预期工作: + +```js + let variable = 'value'; + await ctx.cache.tryGet('cache:key', async () => { + variable = 'new value'; + const newVariable = 'new variable'; + return newVariable; + }) + console.log(variable); // 缓存未命中: 'new value', 缓存命中: 'value' + +``` + +::: + +## API + +### ctx.cache.tryGet(key, getValueFunc \[, maxAge \[, refresh ]]) + +#### 参数 + +| 名称 | 类型 | 描述 | +| ------------ | ---------------------- | ---------------------------------------------------------- | +| key | `string` | *(必填)* 用于存储和获取缓存的键。您可以使用 `:` 作为分隔符创建层次结构。 | +| getValueFunc | `function` \| `string` | *(必填)* 当发生缓存未命中时返回要缓存的数据的函数。 | +| maxAge | `number` | *(可选)* 缓存的最大过期时间(以秒为单位)。如果没有指定,将使用 `CACHE_CONTENT_EXPIRE`。 | +| refresh | `boolean` | *(可选)* 是否在缓存命中时更新缓存过期时间。默认为 `true`。 | + +#### 定义在 + +[lib/middleware/cache/index.js](https://github.com/DIYgod/RSSHub/blob/master/lib/middleware/cache/index.js#L58) + +::: tip 提示 +以下是使用缓存的高级方法。大多数情况下,您应使用 `ctx.cache.tryGet()`。 + +请注意,当使用 `ctx.cache.get()` 获取缓存时,您需要使用 `JSON.parse()`。 +::: + +### ctx.cache.get(key \[, refresh ]) + +#### 参数 + +| 名称 | 类型 | 描述 | +| ------- | --------- | -------------------------------------- | +| key | `string` | *(必填)* 用于检索缓存的键。您可以使用 `:` 作为分隔符创建层次结构。 | +| refresh | `boolean` | *(可选)* 是否在缓存命中时更新缓存过期时间。默认为`true`。 | + +### ctx.cache.set(key, value \[, maxAge ]) + +#### 参数 + +| 名称 | 类型 | 描述 | +| ------ | --------------------- | ----------------------------------------------------------- | +| key | `string` | *(必填)* 用于存储缓存的键。您可以使用`:`作为分隔符创建层次结构。 | +| value | `function`\| `string` | *(必填)* 要缓存的值。 | +| maxAge | `number` | *(可选)* 缓存的最大过期时间 (以秒为单位)。如果没有指定,将使用 `CACHE_CONTENT_EXPIRE`。 | diff --git a/docs/support/README.md b/docs/support/README.md index 8b7bd0d4dbd45e..0bf4c3d45f6c88 100644 --- a/docs/support/README.md +++ b/docs/support/README.md @@ -13,6 +13,7 @@ RSSHub 是采用 MIT 许可的开源项目,使用完全免费。但是随着 周期性赞助可以获得额外的回报,比如更快的 GitHub 响应或者你的名字会出现在 RSSHub 的 GitHub 仓库和现在我们的官网中. - 通过 [GitHub](https://github.com/sponsors/DIYgod) 赞助 +- 通过 [Open Collective](https://opencollective.com/RSSHub) 赞助 - 通过 [Patreon](https://www.patreon.com/DIYgod) 赞助 - 通过 [爱发电](https://afdian.net/@diygod) 赞助 - 给我们发邮件联系赞助事宜: From c09a28bf11f9f15ed97d933e76714e1d902d5658 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 29 Mar 2023 14:38:16 +0000 Subject: [PATCH 3/8] style: auto format --- docs/joinus/use-cache.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/joinus/use-cache.md b/docs/joinus/use-cache.md index bb823aa5419828..502211aa8ffeb4 100644 --- a/docs/joinus/use-cache.md +++ b/docs/joinus/use-cache.md @@ -47,12 +47,12 @@ #### 参数 -| 名称 | 类型 | 描述 | -| ------------ | ---------------------- | ---------------------------------------------------------- | -| key | `string` | *(必填)* 用于存储和获取缓存的键。您可以使用 `:` 作为分隔符创建层次结构。 | -| getValueFunc | `function` \| `string` | *(必填)* 当发生缓存未命中时返回要缓存的数据的函数。 | +| 名称 | 类型 | 描述 | +| ------------ | ---------------------- | ------------------------------------------------------------------------------------------ | +| key | `string` | *(必填)* 用于存储和获取缓存的键。您可以使用 `:` 作为分隔符创建层次结构。 | +| getValueFunc | `function` \| `string` | *(必填)* 当发生缓存未命中时返回要缓存的数据的函数。 | | maxAge | `number` | *(可选)* 缓存的最大过期时间(以秒为单位)。如果没有指定,将使用 `CACHE_CONTENT_EXPIRE`。 | -| refresh | `boolean` | *(可选)* 是否在缓存命中时更新缓存过期时间。默认为 `true`。 | +| refresh | `boolean` | *(可选)* 是否在缓存命中时更新缓存过期时间。默认为 `true`。 | #### 定义在 @@ -68,17 +68,17 @@ #### 参数 -| 名称 | 类型 | 描述 | -| ------- | --------- | -------------------------------------- | +| 名称 | 类型 | 描述 | +| ------- | --------- | -------------------------------------------------------------------- | | key | `string` | *(必填)* 用于检索缓存的键。您可以使用 `:` 作为分隔符创建层次结构。 | -| refresh | `boolean` | *(可选)* 是否在缓存命中时更新缓存过期时间。默认为`true`。 | +| refresh | `boolean` | *(可选)* 是否在缓存命中时更新缓存过期时间。默认为`true`。 | ### ctx.cache.set(key, value \[, maxAge ]) #### 参数 -| 名称 | 类型 | 描述 | -| ------ | --------------------- | ----------------------------------------------------------- | +| 名称 | 类型 | 描述 | +| ------ | --------------------- | ----------------------------------------------------------------------------------------- | | key | `string` | *(必填)* 用于存储缓存的键。您可以使用`:`作为分隔符创建层次结构。 | -| value | `function`\| `string` | *(必填)* 要缓存的值。 | +| value | `function`\| `string` | *(必填)* 要缓存的值。 | | maxAge | `number` | *(可选)* 缓存的最大过期时间 (以秒为单位)。如果没有指定,将使用 `CACHE_CONTENT_EXPIRE`。 | From 2b7be504d96b0054aef77b361a9e5ca15b56aa96 Mon Sep 17 00:00:00 2001 From: TonyRL Date: Wed, 29 Mar 2023 14:47:35 +0000 Subject: [PATCH 4/8] docs: fix typos --- docs/.vuepress/config.js | 2 +- docs/joinus/debug.md | 2 +- docs/joinus/new-rss/add-docs.md | 2 +- docs/joinus/script-standard.md | 2 -- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 7e09cc25fb2e1a..73b6fcd9d8580f 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -131,7 +131,7 @@ module.exports = { path: '/joinus/new-radar.html', }, { - title: '💪 Advanced', + title: '💪 高级用法', path: '/joinus/advanced-feed.html', collapsable: false, children: [ diff --git a/docs/joinus/debug.md b/docs/joinus/debug.md index a37eecd8c8eea1..ca4466d90da01e 100644 --- a/docs/joinus/debug.md +++ b/docs/joinus/debug.md @@ -3,7 +3,7 @@ sidebarDepth: 0 --- # 调试 -当调试代码时,除了使用 `console.log` 或将 node 进程附加到调试器,您还可以将自定义对象提供给 ctx.state.json 来进行调试。 +当调试代码时,除了使用 `console.log` 或将 node 进程附加到调试器,您还可以将自定义对象提供给 `ctx.state.json` 来进行调试。 ## 使用 `ctx.state.json` diff --git a/docs/joinus/new-rss/add-docs.md b/docs/joinus/new-rss/add-docs.md index 52d5b86d8b8bf7..ce3e2a5b3481f0 100644 --- a/docs/joinus/new-rss/add-docs.md +++ b/docs/joinus/new-rss/add-docs.md @@ -4,7 +4,7 @@ sidebarDepth: 2 # 添加文档 -现在我们完成了代码,是时候为您的路由添加文档了。在 [文档(/docs/)](https://github.com/DIYgod/RSSHub/blob/master/docs/en)中打开相应的文件,本例中是 `docs/programming.md`。您可以通过运行以下命令实时预览文档: +现在我们完成了代码,是时候为您的路由添加文档了。在 [文档 (/docs/)](https://github.com/DIYgod/RSSHub/blob/master/docs) 中打开相应的文件,本例中是 `docs/programming.md`。您可以通过运行以下命令实时预览文档: diff --git a/docs/joinus/script-standard.md b/docs/joinus/script-standard.md index d2aef959662dc5..220667356dabec 100644 --- a/docs/joinus/script-standard.md +++ b/docs/joinus/script-standard.md @@ -65,8 +65,6 @@ sidebarDepth: 2 ## v2 路由规范 -When creating a new route in RSSHub, you need to organize your files in a specific way. Your namespace folder should be stored in the `lib/v2` directory and should include three mandatory files: - 当在 RSSHub 中编写新的路由时,需要按特定方式组织文件。命名空间文件夹应该存储在 `lib/v2` 目录下,并且应包括三个必需文件: - `router.js` 注册路由 From eed561dee7d8a949e76a63bcd9706be0031e2fc4 Mon Sep 17 00:00:00 2001 From: Evan Date: Wed, 29 Mar 2023 11:30:27 -0700 Subject: [PATCH 5/8] feat(inoreader): rss (#12196) * feat(inoreader): rss * Update docs/en/reading.md * Update docs/reading.md --------- --- docs/en/reading.md | 6 ++++++ docs/reading.md | 7 +++++++ lib/v2/inoreader/maintainer.js | 1 + lib/v2/inoreader/radar.js | 6 ++++++ lib/v2/inoreader/router.js | 1 + lib/v2/inoreader/rss.js | 22 ++++++++++++++++++++++ 6 files changed, 43 insertions(+) create mode 100644 lib/v2/inoreader/rss.js diff --git a/docs/en/reading.md b/docs/en/reading.md index c79de7b7bebea8..8cd10f2fdd543c 100644 --- a/docs/en/reading.md +++ b/docs/en/reading.md @@ -35,6 +35,12 @@ Eg: +### RSS + + ## kakuyomu diff --git a/docs/reading.md b/docs/reading.md index 0be0e19ae20f4d..8a3ca6b801ecd1 100644 --- a/docs/reading.md +++ b/docs/reading.md @@ -35,6 +35,13 @@ pageClass: routes
+### RSS + + + ## kakuyomu ### 章节更新 diff --git a/lib/v2/inoreader/maintainer.js b/lib/v2/inoreader/maintainer.js index 3860a6299a6abb..b8c34cc425e894 100644 --- a/lib/v2/inoreader/maintainer.js +++ b/lib/v2/inoreader/maintainer.js @@ -1,3 +1,4 @@ module.exports = { '/html_clip/:user/:tag/:num?': ['BeautyyuYanli'], + '/rss/:user/:tag': ['NavePnow'], }; diff --git a/lib/v2/inoreader/radar.js b/lib/v2/inoreader/radar.js index 319625db5c6d70..357e8cb6af4b4b 100644 --- a/lib/v2/inoreader/radar.js +++ b/lib/v2/inoreader/radar.js @@ -12,6 +12,12 @@ module.exports = { return `/inoreader/html_clip/${params.user}/${params.tag}` + (limit ? `?limit=${limit}` : ''); }, }, + { + title: 'RSS', + docs: 'https://docs.rsshub.app/reading.html#inoreader', + source: ['/stream/user/:user/tag/:tag'], + target: (params) => `/inoreader/rss/${params.user}/${params.tag}`, + }, ], }, }; diff --git a/lib/v2/inoreader/router.js b/lib/v2/inoreader/router.js index d8961e1bb990b9..af578b203b85f8 100644 --- a/lib/v2/inoreader/router.js +++ b/lib/v2/inoreader/router.js @@ -1,3 +1,4 @@ module.exports = function (router) { router.get('/html_clip/:user/:tag', require('./index')); + router.get('/rss/:user/:tag', require('./rss')); }; diff --git a/lib/v2/inoreader/rss.js b/lib/v2/inoreader/rss.js new file mode 100644 index 00000000000000..0357cae16eea39 --- /dev/null +++ b/lib/v2/inoreader/rss.js @@ -0,0 +1,22 @@ +const parser = require('@/utils/rss-parser'); + +module.exports = async (ctx) => { + const user = ctx.params.user; + const tag = ctx.params.tag; + const rootUrl = 'https://www.inoreader.com/stream'; + const rssUrl = `${rootUrl}/user/${user}/tag/${tag}`; + const feed = await parser.parseURL(rssUrl); + feed.items = feed.items.map((item) => ({ + title: item.title, + pubDate: item.pubDate, + link: item.link, + description: item.content, + category: item.categories, + })); + ctx.state.data = { + title: feed.title, + link: feed.link, + description: feed.description, + item: feed.items, + }; +}; From a7c9154e059a771db8ae9fa2cda910f322c717d2 Mon Sep 17 00:00:00 2001 From: lyqluis <39592732+lyqluis@users.noreply.github.com> Date: Thu, 30 Mar 2023 03:34:25 +0800 Subject: [PATCH 6/8] fix: segmentfault/channel (#12191) * chore(deps): bump proxy-chain from 2.2.1 to 2.3.0 Bumps [proxy-chain](https://github.com/apify/proxy-chain) from 2.2.1 to 2.3.0. - [Release notes](https://github.com/apify/proxy-chain/releases) - [Changelog](https://github.com/apify/proxy-chain/blob/master/CHANGELOG.md) - [Commits](https://github.com/apify/proxy-chain/compare/v2.2.1...v2.3.0) --- updated-dependencies: - dependency-name: proxy-chain dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * fix(route): /segmentfault/channel * style: auto format --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- lib/v2/segmentfault/channel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/v2/segmentfault/channel.js b/lib/v2/segmentfault/channel.js index de62204f148378..5360159045e63d 100644 --- a/lib/v2/segmentfault/channel.js +++ b/lib/v2/segmentfault/channel.js @@ -16,8 +16,8 @@ module.exports = async (ctx) => { const list = $('ul.bg-transparent.list-group.list-group-flush > li') .slice(0, 10) .map((_, item) => ({ - link: new URL($(item).find('div.content > h5 > a').attr('href'), host).href, - title: $(item).find('div.content > h5 > a').text(), + link: new URL($(item).find('div.content > h3.h5 > a').attr('href'), host).href, + title: $(item).find('div.content > h3.h5 > a').text(), author: $(item).find('span.name').text(), })) .get(); From fff92cde1fb00995053ea2a5f3cbc1b78191172e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Mar 2023 22:04:14 +0000 Subject: [PATCH 7/8] chore(deps): bump puppeteer from 19.8.0 to 19.8.2 (#12199) Bumps [puppeteer](https://github.com/puppeteer/puppeteer) from 19.8.0 to 19.8.2. - [Release notes](https://github.com/puppeteer/puppeteer/releases) - [Changelog](https://github.com/puppeteer/puppeteer/blob/main/release-please-config.json) - [Commits](https://github.com/puppeteer/puppeteer/compare/puppeteer-v19.8.0...puppeteer-v19.8.2) --- updated-dependencies: - dependency-name: puppeteer dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 69 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 5cce293fa4a9d4..c106eb13f9fc35 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "pidusage": "3.0.2", "plist": "3.0.6", "proxy-chain": "2.3.0", - "puppeteer": "19.8.0", + "puppeteer": "19.8.2", "puppeteer-extra": "3.3.6", "puppeteer-extra-plugin-stealth": "2.11.2", "query-string": "7.1.3", diff --git a/yarn.lock b/yarn.lock index c33f97cecbbbd5..537ff7ffda437c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1574,6 +1574,20 @@ dependencies: safe-buffer "^5.0.1" +"@puppeteer/browsers@0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-0.3.1.tgz#21d43c49f6f8bb5fa905b322d73c11fca9b515bd" + integrity sha512-WTFVqWY7ipI+CsJRb6vEddYJ/lMl2bHYP5BjFDBM9JMKPjJNT4psXz6DLuJpi3EkG80Q0ef5Azzv3gKu9MvMUg== + dependencies: + debug "4.3.4" + extract-zip "2.0.1" + https-proxy-agent "5.0.1" + progress "2.0.3" + proxy-from-env "1.1.0" + tar-fs "2.1.1" + unbzip2-stream "1.4.3" + yargs "17.7.1" + "@rollup/pluginutils@^4.0.0": version "4.2.1" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d" @@ -3906,10 +3920,10 @@ chrome-trace-event@^1.0.2: resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== -chromium-bidi@0.4.5: - version "0.4.5" - resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-0.4.5.tgz#a352e755536dde609bd2c77e4b1f0906bff8784e" - integrity sha512-rkav9YzRfAshSTG3wNXF7P7yNiI29QAo1xBXElPoCoSQR5n20q3cOyVhDv6S7+GlF/CJ/emUxlQiR0xOPurkGg== +chromium-bidi@0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-0.4.6.tgz#a082151834083ed002624f12fa35e748817b2ee5" + integrity sha512-TQOkWRaLI/IWvoP8XC+7jO4uHTIiAUiklXU1T0qszlUFEai9LgKXIBXy3pOS3EnQZ3bQtMbKUPkug0fTAEHCSw== dependencies: mitt "3.0.0" @@ -11323,12 +11337,12 @@ pupa@^2.0.1: dependencies: escape-goat "^2.0.0" -puppeteer-core@19.8.0: - version "19.8.0" - resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-19.8.0.tgz#0152f652a64274f93f681b52ed03baf7de7905dd" - integrity sha512-5gBkLR9nae7chWDhI3mpj5QA+hPmjEOW29qw5ap5g51Uo5Lxe5Yip1uyQwZSjg5Wn/eyE9grh2Lyx3m8rPK90A== +puppeteer-core@19.8.1: + version "19.8.1" + resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-19.8.1.tgz#c592db9c524d418c91b9dd6d3f3dac00b9834901" + integrity sha512-7yPMzusYRvJ/laLaMFIQ01E9WakKkJJXLsMAymH6y5EKqYe5hhn68/SbvF+1YJMb843GftU7gT86Fw8y5jkn3w== dependencies: - chromium-bidi "0.4.5" + chromium-bidi "0.4.6" cross-fetch "3.1.5" debug "4.3.4" devtools-protocol "0.0.1107588" @@ -11386,16 +11400,17 @@ puppeteer-extra@3.3.6: debug "^4.1.1" deepmerge "^4.2.2" -puppeteer@19.8.0: - version "19.8.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-19.8.0.tgz#2d2225fb24ba6813cd31304d41c6b8340c9f3582" - integrity sha512-MpQClmttCUxv4bVokX/YSXLCU12CUApuRf0rIJyGknYcIrDQNkLUx1N7hNt88Ya4lq9VDsdiDEJ3bcPijqJYPQ== +puppeteer@19.8.2: + version "19.8.2" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-19.8.2.tgz#e8a3a9aa725b5d445f97454cf13ad9007709576b" + integrity sha512-LPdNVYMR6ddp4YS3GK1bqKsasCJj1aZjt9dNOKcnzKezuMoishlHY6bCnFLjTc34iqnGzIrJp07TQ9M+aML2+g== dependencies: + "@puppeteer/browsers" "0.3.1" cosmiconfig "8.1.3" https-proxy-agent "5.0.1" progress "2.0.3" proxy-from-env "1.1.0" - puppeteer-core "19.8.0" + puppeteer-core "19.8.1" pure-rand@^6.0.0: version "6.0.1" @@ -14530,6 +14545,19 @@ yargs-parser@^21.1.1: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== +yargs@17.7.1, yargs@^17.3.1: + version "17.7.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967" + integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + yargs@^13.3.2: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" @@ -14546,19 +14574,6 @@ yargs@^13.3.2: y18n "^4.0.0" yargs-parser "^13.1.2" -yargs@^17.3.1: - version "17.7.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967" - integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - yauzl@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" From c5d6b6b942961a525e7c5dad93a455b4a9c3a673 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Mar 2023 22:18:42 +0000 Subject: [PATCH 8/8] chore(deps-dev): bump eslint from 8.36.0 to 8.37.0 (#12200) Bumps [eslint](https://github.com/eslint/eslint) from 8.36.0 to 8.37.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v8.36.0...v8.37.0) --- updated-dependencies: - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 52 ++++++++++++++++++++++++++-------------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index c106eb13f9fc35..1cf79f71174aa4 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "@vuepress/plugin-pwa": "1.9.9", "@vuepress/shared-utils": "1.9.9", "cross-env": "7.0.3", - "eslint": "8.36.0", + "eslint": "8.37.0", "eslint-config-prettier": "8.8.0", "eslint-plugin-prettier": "4.2.1", "eslint-plugin-yml": "1.5.0", diff --git a/yarn.lock b/yarn.lock index 537ff7ffda437c..f4e768e80f1072 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1007,14 +1007,14 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.4.0.tgz#3e61c564fcd6b921cb789838631c5ee44df09403" integrity sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ== -"@eslint/eslintrc@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.1.tgz#7888fe7ec8f21bc26d646dbd2c11cd776e21192d" - integrity sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw== +"@eslint/eslintrc@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.2.tgz#01575e38707add677cf73ca1589abba8da899a02" + integrity sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.5.0" + espree "^9.5.1" globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -1022,10 +1022,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.36.0": - version "8.36.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.36.0.tgz#9837f768c03a1e4a30bd304a64fb8844f0e72efe" - integrity sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg== +"@eslint/js@8.37.0": + version "8.37.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.37.0.tgz#cf1b5fa24217fe007f6487a26d765274925efa7d" + integrity sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A== "@gar/promisify@^1.1.3": version "1.1.3" @@ -5683,20 +5683,20 @@ eslint-scope@^7.1.1: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" - integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== +eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" + integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== -eslint@8.36.0, eslint@^8.9.0: - version "8.36.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.36.0.tgz#1bd72202200a5492f91803b113fb8a83b11285cf" - integrity sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw== +eslint@8.37.0, eslint@^8.9.0: + version "8.37.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.37.0.tgz#1f660ef2ce49a0bfdec0b0d698e0b8b627287412" + integrity sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" - "@eslint/eslintrc" "^2.0.1" - "@eslint/js" "8.36.0" + "@eslint/eslintrc" "^2.0.2" + "@eslint/js" "8.37.0" "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -5707,8 +5707,8 @@ eslint@8.36.0, eslint@^8.9.0: doctrine "^3.0.0" escape-string-regexp "^4.0.0" eslint-scope "^7.1.1" - eslint-visitor-keys "^3.3.0" - espree "^9.5.0" + eslint-visitor-keys "^3.4.0" + espree "^9.5.1" esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -5734,14 +5734,14 @@ eslint@8.36.0, eslint@^8.9.0: strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.5.0: - version "9.5.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.0.tgz#3646d4e3f58907464edba852fa047e6a27bdf113" - integrity sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw== +espree@^9.5.1: + version "9.5.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4" + integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg== dependencies: acorn "^8.8.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.3.0" + eslint-visitor-keys "^3.4.0" esprima@^4.0.0, esprima@^4.0.1: version "4.0.1"