diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000000000..77026d75ece61f --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,64 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/devcontainers/images/blob/v0.3.24/src/javascript-node/.devcontainer/devcontainer.json +{ + "name": "Node.js", + "image": "mcr.microsoft.com/devcontainers/javascript-node:22-bookworm", + + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "cweijan.vscode-database-client2", + "dbaeumer.vscode-eslint", + "eamodio.gitlens", + "EditorConfig.EditorConfig", + "esbenp.prettier-vscode", + "deepscan.vscode-deepscan", + "SonarSource.sonarlint-vscode", + "unifiedjs.vscode-mdx", + "VASubasRaj.flashpost", // Thunder Client is paywalled in WSL/Codespaces/SSH > 2.30.0 + "ZihanLi.at-helper" + ] + } + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [1200, 3000], + + "portsAttributes": { + "1200": { + "label": "app port", + "onAutoForward": "notify" + }, + "3000": { + "label": "docs port", + "onAutoForward": "notify" + } + }, + + "onCreateCommand": "sudo apt-get update && export DEBIAN_FRONTEND=noninteractive && sudo apt-get -y install --no-install-recommends ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 libcairo2 libcups2 libdbus-1-3 libexpat1 libgbm1 libglib2.0-0 libnspr4 libnss3 libpango-1.0-0 libx11-6 libxcb1 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxkbcommon0 libxrandr2 wget xdg-utils redis-server default-jre-headless && sudo apt-get autoremove -y && sudo apt-get clean -y && sudo rm -rf /var/lib/apt/lists/*", + + "updateContentCommand": "export JAVA_HOME=/usr/lib/jvm/default-java && pnpm config set store-dir ~/.local/share/pnpm/store && pnpm i && pnpm rb", + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "pnpm i && pnpm rb", + + // Disable auto start dev env since codespaces sometimes fails to attach to the terminal + // "postAttachCommand": { + // "app": "pnpm i", + // // "docs": "pnpm -C website start" + // }, + + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "node", + "features": { + "ghcr.io/devcontainers/features/docker-in-docker": { + "version": "latest" + }, + "ghcr.io/devcontainers/features/github-cli": { + "version": "latest" + } + } +} diff --git a/.dockerignore b/.dockerignore index 6816be3151e5ad..81d65ee79f4eb1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,35 +1,45 @@ +# folders +.devcontainer .github +.husky .idea .vscode Dockerfile* LICENSE Procfile app-minimal -assets coverage -docs node_modules test + +# files .codecov.yml .dockerignore .editorconfig .env .eslint* .gitignore +.gitpod.yml +.markdownlint.jsonc .prettier* .(yarn|npm|nvm)rc *.md app.json +eslint.config.mjs docker-compose* +fly.toml jsconfig.json npm-debug.log process.json package-lock.json +vitest.config.ts vercel.json -#git but keep the git commit hash +# git but keep the git commit hash .git/logs -.git/objects .git/index .git/info .git/hooks + +# rsshub auxiliary files +lib/routes/**/radar.js diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 5b176d4301179c..00000000000000 --- a/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -coverage -.vscode -docker-compose.yml -!/.github -!/docs/.vuepress diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index f7f4dd8966ef50..00000000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "extends": ["eslint:recommended", "plugin:prettier/recommended", "plugin:yml/recommended"], - "plugins": ["prettier"], - "parserOptions": { - "ecmaVersion": 2020, - "sourceType": "module" - }, - "env": { - "node": true, - "es6": true, - "browser": true - }, - "rules": { - "no-console": 2, - "block-scoped-var": 2, - "curly": 2, - "eqeqeq": 2, - "no-global-assign": 2, - "no-implicit-globals": 2, - "no-labels": 2, - "no-multi-str": 2, - "comma-spacing": 2, - "comma-style": 2, - "no-implicit-coercion": [ - "error", - { - "boolean": false, - "number": false, - "string": false, - "disallowTemplateShorthand": true - } - ], - "func-call-spacing": 2, - "keyword-spacing": 2, - "linebreak-style": 2, - "lines-around-comment": 2, - "no-multiple-empty-lines": 2, - "space-infix-ops": 2, - "arrow-spacing": 2, - "no-var": 2, - "prefer-const": 2, - "no-unsafe-negation": 2, - "array-callback-return": 2, - "dot-notation": 2, - "no-eval": 2, - "no-extend-native": 2, - "no-extra-label": 2, - "semi": 2, - "space-before-blocks": 2, - "space-in-parens": 2, - "space-unary-ops": 2, - "spaced-comment": 2, - "arrow-body-style": 2, - "arrow-parens": 2, - "no-restricted-imports": 2, - "no-duplicate-imports": 2, - "no-useless-computed-key": 2, - "no-useless-rename": 2, - "rest-spread-spacing": 2, - "no-trailing-spaces": 2, - "no-control-regex": 0, - "prettier/prettier": 0, - "no-await-in-loop": 2, - "require-atomic-updates": 0, - "no-prototype-builtins": 0, - "no-new-func": 2, - "require-await": 2, - "prefer-arrow-callback": 2, - "object-shorthand": 2, - "yml/quotes": [ - "error", - { - "prefer": "single" - } - ] - }, - "overrides": [ - { - "files": ["*.yaml", "*.yml"], - "parser": "yaml-eslint-parser", - "rules": { - "lines-around-comment": [ - "error", - { - "beforeBlockComment": false - } - ] - } - } - ] -} diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index bebc156ede8682..00000000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,4 +0,0 @@ -# These are supported funding model platforms -github: DIYgod -patreon: DIYgod -custom: ['https://afdian.net/@diygod', 'https://diygod.me/images/zfb.jpg', 'https://diygod.me/images/wx.jpg'] diff --git a/.github/ISSUE_TEMPLATE/bug_report_en.yml b/.github/ISSUE_TEMPLATE/bug_report_en.yml index ac686b2dcc909a..de560dad5979cc 100644 --- a/.github/ISSUE_TEMPLATE/bug_report_en.yml +++ b/.github/ISSUE_TEMPLATE/bug_report_en.yml @@ -6,7 +6,7 @@ body: - type: markdown attributes: value: | - Please ensure you have read [documentation](https://docs.rsshub.app/en), and provide all the information required by this template, otherwise the issue will be closed immediately. + Please ensure you have read [documentation](https://docs.rsshub.app/), and provide all the information required by this template, otherwise the issue will be closed immediately. Due to the anti-crawling policy implemented by certain websites, some RSS routes provided by the demo will return status code 403. This is not an issue caused by RSSHub and please do not report it. - type: textarea diff --git a/.github/ISSUE_TEMPLATE/bug_report_zh.yml b/.github/ISSUE_TEMPLATE/bug_report_zh.yml index d0a646ac265f9d..3225ed9095007d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report_zh.yml +++ b/.github/ISSUE_TEMPLATE/bug_report_zh.yml @@ -69,7 +69,7 @@ body: label: 部署相关信息 description: | 请提供您的操作系统、node 版本和(如果适用) docker 版本。 - 请确保您部署的是[主线 master 分支](https://github.com/DIYgod/RSSHub/tree/master)最新版 RSSHub。 + 请确保您部署的是 [主线 master 分支](https://github.com/DIYgod/RSSHub/tree/master) 最新版 RSSHub。 placeholder: 'OS: Linux, Node: v10.15.3, Docker: v19.03.13' - type: textarea @@ -86,5 +86,5 @@ body: attributes: label: 这不是重复的 issue options: - - label: 我已经搜索了[现有 issue](https://github.com/DIYgod/RSSHub/issues),以确保该错误尚未被报告。 + - label: 我已经搜索了 [现有 issue](https://github.com/DIYgod/RSSHub/issues),以确保该错误尚未被报告。 required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request_en.yml b/.github/ISSUE_TEMPLATE/feature_request_en.yml index ed5db239e07fe0..7aee701cc8e5d9 100644 --- a/.github/ISSUE_TEMPLATE/feature_request_en.yml +++ b/.github/ISSUE_TEMPLATE/feature_request_en.yml @@ -7,7 +7,7 @@ body: - type: markdown attributes: value: | - Please ensure the feature requested is not listed in [documentation](https://docs.rsshub.app/en) or [issue](https://github.com/DIYgod/RSSHub/issues), and is not a [new RSS proposal](https://github.com/DIYgod/RSSHub/issues/new?assignees=&labels=RSS+proposal&template=rss_request_en.yml), and provide all the information required by this template. + Please ensure the feature requested is not listed in [documentation](https://docs.rsshub.app/) or [issue](https://github.com/DIYgod/RSSHub/issues), and is not a [new RSS proposal](https://github.com/DIYgod/RSSHub/issues/new?assignees=&labels=RSS+proposal&template=rss_request_en.yml), and provide all the information required by this template. Otherwise the issue will be closed immediately. - type: textarea diff --git a/.github/ISSUE_TEMPLATE/rss_request_en.yml b/.github/ISSUE_TEMPLATE/rss_request_en.yml index 20340b6be7f516..0f1efc64b5c915 100644 --- a/.github/ISSUE_TEMPLATE/rss_request_en.yml +++ b/.github/ISSUE_TEMPLATE/rss_request_en.yml @@ -1,4 +1,4 @@ -name: 🍰 RSS Proposal +name: 🧡 RSS Proposal description: Submit a new RSS proposal labels: ['RSS proposal'] @@ -7,10 +7,10 @@ body: - type: markdown attributes: value: | - Please ensure the RSS proposal is not listed in [documentation](https://docs.rsshub.app/en) or [issue](https://github.com/DIYgod/RSSHub/issues), website doesn't provide this kind of RSS feed, and provide all the information required by this template. + Please ensure the RSS proposal is not listed in [documentation](https://docs.rsshub.app/) or [issue](https://github.com/DIYgod/RSSHub/issues), website doesn't provide this kind of RSS feed, and provide all the information required by this template. Otherwise the issue will be closed immediately. - We are flooded with feature requests and short-handed, please try to make it yourself, the [guide](https://docs.rsshub.app/en/joinus) is a good place to start. Submit a pull request when done! + We are flooded with feature requests and short-handed, please try to make it yourself, the [guide](https://docs.rsshub.app/joinus) is a good place to start. Submit a pull request when done! - type: dropdown id: category @@ -27,7 +27,7 @@ body: - Design - Live - Multimedia - - Pciture + - Picture - ACG - Application Updates - University diff --git a/.github/ISSUE_TEMPLATE/rss_request_zh.yml b/.github/ISSUE_TEMPLATE/rss_request_zh.yml index 501994bee268e7..2970aa6381e330 100644 --- a/.github/ISSUE_TEMPLATE/rss_request_zh.yml +++ b/.github/ISSUE_TEMPLATE/rss_request_zh.yml @@ -1,4 +1,4 @@ -name: 🍰 RSS 提案 +name: 🧡 RSS 提案 description: 提交新的 RSS 提案 labels: ['RSS proposal'] @@ -10,7 +10,7 @@ body: 请确保 [文档](https://docs.rsshub.app) 和 [issue](https://github.com/DIYgod/RSSHub/issues) 中没有相关内容,且源站没有提供 RSS,并按照模版提供信息 否则 issue 将被立即关闭 - 目前 RSS 提案滞销,如有能力请按照 [指南](https://docs.rsshub.app/joinus) 自行编写并提交 PR + 目前 RSS 提案滞销,如有能力请按照 [指南](https://docs.rsshub.app/joinus/quick-start) 自行编写并提交 PR - type: dropdown id: category diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ec6b5bfaa1fbb0..e6ae2dd496121c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,47 +1,44 @@ -## 该 PR 相关 Issue / Involved issue +## Involved Issue / 该 PR 相关 Issue Close # -## 完整路由地址 / Example for the proposed route(s) +## Example for the Proposed Route(s) / 路由地址示例 ```routes ``` -## 新 RSS 检查列表 / New RSS Script Checklist +## New RSS Route Checklist / 新 RSS 路由检查表 -- [ ] 新的路由 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 +- [ ] New Route / 新的路由 + - [ ] Follows [Script Standard](https://docs.rsshub.app/joinus/advanced/script-standard) / 跟随 [路由规范](https://docs.rsshub.app/zh/joinus/advanced/script-standard) +- [ ] Anti-bot or rate limit / 反爬/频率限制 + - [ ] If yes, do your code reflect this sign? / 如果有, 是否有对应的措施? +- [ ] [Date and time](https://docs.rsshub.app/joinus/advanced/pub-date) / [日期和时间](https://docs.rsshub.app/zh/joinus/advanced/pub-date) + - [ ] Parsed / 可以解析 + - [ ] Correct time zone / 时区正确 +- [ ] New package added / 添加了新的包 - [ ] `Puppeteer` -## 说明 / Note +## Note / 说明 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1eccb7e08ad59d..72d8543ed327e7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,37 +4,19 @@ updates: directory: '/' schedule: interval: daily - time: '21:00' - open-pull-requests-limit: 10 + time: '08:00' + open-pull-requests-limit: 100 labels: - dependencies ignore: - - dependency-name: got - versions: ['>=12.0.0'] - - dependency-name: ip-regex - versions: ['>=5.0.0'] - - dependency-name: remark - versions: ['>=14.0.0'] - - dependency-name: remark-frontmatter - versions: ['>=4.0.0'] - - dependency-name: remark-gfm - versions: ['>=2.0.0'] - - dependency-name: remark-parse - versions: ['>=10.0.0'] - - dependency-name: remark-preset-prettier - versions: ['>=1.0.0'] - - dependency-name: remark-stringify - versions: ['>=10.0.0'] - - dependency-name: string-width - versions: ['>=5.0.0'] - - dependency-name: unified - versions: ['>=10.0.0'] + - dependency-name: jsrsasign + versions: ['>=11.0.0'] # no longer includes KJUR.crypto.Cipher for RSA - package-ecosystem: 'github-actions' directory: '/' schedule: interval: daily - time: '21:00' - open-pull-requests-limit: 10 + time: '08:00' + open-pull-requests-limit: 100 labels: - dependencies diff --git a/.github/labeler.yml b/.github/labeler.yml index ae4b970cf5032c..5838333146c094 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,13 +1,17 @@ -'Route: v1': -- lib/router.js -- any: ['lib/routes/**/*.js', '!lib/routes/index.js'] +'Route: deprecated': +- changed-files: + - any-glob-to-any-file: ['lib/router.js'] + - all-globs-to-any-file: ['lib/routes-deprecated/**/*.js', '!lib/routes-deprecated/index.js'] -'Route: v2': -- 'lib/v2/**/*.js' +'Route': +- changed-files: + - any-glob-to-any-file: ['lib/routes/**/*.ts'] core enhancement: -- lib/routes/index.js -- any: ['lib/**', '!lib/radar-rules.js', '!lib/router.js', '!lib/routes/**', '!lib/v2/**'] +- changed-files: + - any-glob-to-any-file: ['lib/routes/index.ts'] + - all-globs-to-any-file: ['lib/**', '!lib/config.ts', '!lib/router.js', '!lib/routes/**', '!lib/routes-deprecated/**'] dependencies: -- yarn.lock +- changed-files: + - any-glob-to-any-file: ['package.json', 'pnpm-lock.yaml', 'yarn.lock'] diff --git a/.github/workflows/build-assets.yml b/.github/workflows/build-assets.yml index 2eaa3e05861711..e824ab20547146 100644 --- a/.github/workflows/build-assets.yml +++ b/.github/workflows/build-assets.yml @@ -1,31 +1,69 @@ -name: build assets +name: Build assets on: + workflow_dispatch: push: branches: - master paths: - - 'lib/**' - - 'scripts/workflow/*.js' + - 'lib/**/*.ts' jobs: build: runs-on: ubuntu-latest name: Build assets + timeout-minutes: 5 + permissions: + contents: write steps: - name: Checkout - uses: actions/checkout@v3 - - name: Use Node.js v16 - uses: actions/setup-node@v3 + uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Use Node.js Active LTS + uses: actions/setup-node@v4 with: - node-version: 16 - cache: 'yarn' - - name: Build file - run: yarn && npm run build:all + node-version: lts/* + cache: 'pnpm' + - name: Install dependencies (yarn) + run: pnpm i + - name: Build assets + run: pnpm build - name: Deploy - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./assets user_name: 'github-actions[bot]' user_email: '41898282+github-actions[bot]@users.noreply.github.com' + # prevent deleting build/test-full-routes.json which will break build:docs + keep_files: true + - name: Build docs + run: pnpm build:docs + - id: check-env + env: + DOCS_API_TOKEN: ${{ secrets.DOCS_API_TOKEN }} + if: ${{ env.DOCS_API_TOKEN != '' }} + run: echo "defined=true" >> $GITHUB_OUTPUT + - name: Checkout docs + uses: actions/checkout@v4 + if: steps.check-env.outputs.defined == 'true' + with: + repository: 'RSSNext/rsshub-docs' + token: ${{ secrets.DOCS_API_TOKEN }} + path: rsshub-docs + - name: Update docs + if: steps.check-env.outputs.defined == 'true' + run: | + cp -r ./assets/build/docs/en/* ./rsshub-docs/src/routes + cp -r ./assets/build/docs/zh/* ./rsshub-docs/src/zh/routes + cp ./lib/types.ts ./rsshub-docs/.vitepress/theme/types.ts + cp ./scripts/workflow/data.ts ./rsshub-docs/.vitepress/config/data.ts + - name: Commit docs + if: steps.check-env.outputs.defined == 'true' + run: | + cd rsshub-docs + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git status + git diff-index --quiet HEAD || (git commit -m "chore: auto build https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA" -a --no-verify && git push "https://${GITHUB_ACTOR}:${{ secrets.DOCS_API_TOKEN }}@github.com/RSSNext/rsshub-docs.git" HEAD:main) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9b9e7ec329f0f2..7985f4e1501b46 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -13,60 +13,71 @@ name: 'CodeQL' on: push: - branches: [master] + branches: ['master'] pull_request: - # The branches below must be a subset of the branches above - branches: [master] + branches: ['master'] schedule: - cron: '15 9 * * 3' jobs: analyze: name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. runs-on: ubuntu-latest + timeout-minutes: 10 permissions: + # required for all workflows + security-events: write + + # only required for workflows in private repositories actions: read contents: read - security-events: write strategy: fail-fast: false matrix: - language: ['javascript'] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + language: ['javascript-typescript'] + # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] + # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. - # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | - # make bootstrap - # make release + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 + with: + category: '/language:${{matrix.language}}' diff --git a/.github/workflows/comment-on-issue.yml b/.github/workflows/comment-on-issue.yml index 4fa53662fecf5e..f0fdb344784301 100644 --- a/.github/workflows/comment-on-issue.yml +++ b/.github/workflows/comment-on-issue.yml @@ -2,24 +2,29 @@ name: Comment on Issue on: issues: - types: [opened] + types: [opened, edited, reopened] jobs: testRoute: name: Call maintainers runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + issues: write + if: github.event.sender.login != 'issuehunt-oss[bot]' steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 # just need its cache + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 with: - node-version: 16 - cache: 'yarn' - - name: Install dependencies (yarn) # needed since we need to parse markdown, so we also use got instead - run: yarn + node-version: lts/* + cache: 'pnpm' + - name: Install dependencies (pnpm) # import remark-parse and unified + run: pnpm i - name: Generate feedback - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: - github-token: ${{secrets.GITHUB_TOKEN}} + github-token: ${{ secrets.GITHUB_TOKEN }} script: | - const script = require(`${process.env.GITHUB_WORKSPACE}/scripts/workflow/test-issue/call-maintainer.js`) - return await script({ github, context, core }) + const { default: callMaintainer } = await import('${{ github.workspace }}/scripts/workflow/test-issue/call-maintainer.mjs') + await callMaintainer({ github, context, core }) diff --git a/.github/workflows/dependabot-fork.yml b/.github/workflows/dependabot-fork.yml index f02408a97b3b43..baeeff107b6ca8 100644 --- a/.github/workflows/dependabot-fork.yml +++ b/.github/workflows/dependabot-fork.yml @@ -7,12 +7,13 @@ jobs: if: github.repository_owner != 'DIYgod' && github.actor == 'dependabot[bot]' runs-on: ubuntu-latest name: Ignore dependabot on forks + timeout-minutes: 5 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Comment Dependabot PR - uses: thollander/actions-comment-pull-request@v1 + uses: thollander/actions-comment-pull-request@v3 with: message: '@dependabot ignore this dependency' GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 0f94343004a5ce..1d8c5b6bd546d6 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -1,4 +1,4 @@ -name: '[docker] CI for releases' +name: 'Docker Release' on: push: @@ -7,19 +7,16 @@ on: paths: - '.github/workflows/docker-release.yml' - 'lib/**' - - '!**/maintainer.js' - - '!**/radar.js' - - '!**/radar-rules.js' + - '!lib/**/*.test.ts' - 'Dockerfile' - - 'package.json' - - 'yarn.lock' - workflow_dispatch: ~ + workflow_dispatch: {} jobs: check-env: permissions: contents: none runs-on: ubuntu-latest + timeout-minutes: 5 outputs: check-docker: ${{ steps.check-docker.outputs.defined }} steps: @@ -27,39 +24,58 @@ jobs: env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} if: ${{ env.DOCKER_USERNAME != '' }} - run: echo "::set-output name=defined::true" + run: echo "defined=true" >> $GITHUB_OUTPUT release: runs-on: ubuntu-latest needs: check-env if: needs.check-env.outputs.check-docker == 'true' + timeout-minutes: 60 + permissions: + packages: write + id-token: write steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@v3 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - - name: Login to DockerHub - uses: docker/login-action@v2 + - name: Log in to Docker Hub + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract Docker metadata (ordinary version) id: meta-ordinary - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: - images: ${{ secrets.DOCKER_USERNAME }}/rsshub + images: | + ${{ secrets.DOCKER_USERNAME }}/rsshub + ghcr.io/${{ github.repository }} tags: | type=raw,value=latest,enable=true type=raw,value={{date 'YYYY-MM-DD'}},enable=true + type=sha,format=long,prefix=,enable=true flavor: latest=false - name: Build and push Docker image (ordinary version) - uses: docker/build-push-action@v3 + id: build-and-push + uses: docker/build-push-action@v6 with: context: . push: true @@ -68,43 +84,56 @@ jobs: platforms: linux/amd64,linux/arm/v7,linux/arm64 cache-from: type=gha,scope=docker-release cache-to: type=gha,mode=max,scope=docker-release - # cache-from: type=registry,ref=${{ secrets.DOCKER_USERNAME }}/rsshub:buildcache - # cache-to: type=registry,ref=${{ secrets.DOCKER_USERNAME }}/rsshub:buildcache,mode=max + + - name: Sign the published Docker image + if: ${{ github.event_name != 'pull_request' }} + env: + COSIGN_EXPERIMENTAL: 'true' + run: echo "${{ steps.meta-ordinary.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build-and-push.outputs.digest }} - name: Extract Docker metadata (Chromium-bundled version) id: meta-chromium-bundled - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: - images: ${{ secrets.DOCKER_USERNAME }}/rsshub + images: | + ${{ secrets.DOCKER_USERNAME }}/rsshub + ghcr.io/${{ github.repository }} tags: | type=raw,value=chromium-bundled,enable=true type=raw,value=chromium-bundled-{{date 'YYYY-MM-DD'}},enable=true + type=sha,format=long,prefix=chromium-bundled-,enable=true flavor: latest=false - name: Build and push Docker image (Chromium-bundled version) - uses: docker/build-push-action@v3 + id: build-and-push-chromium + uses: docker/build-push-action@v6 with: context: . - build-args: PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=0 + build-args: PUPPETEER_SKIP_DOWNLOAD=0 push: true tags: ${{ steps.meta-chromium-bundled.outputs.tags }} labels: ${{ steps.meta-chromium-bundled.outputs.labels }} platforms: linux/amd64,linux/arm/v7,linux/arm64 cache-from: | type=registry,ref=${{ secrets.DOCKER_USERNAME }}/rsshub:chromium-bundled - # type=gha,scope=docker-release # not needed, Docker automatically uses local cache from the builder - # type=registry,ref=${{ secrets.DOCKER_USERNAME }}/rsshub:buildcache cache-to: type=inline,ref=${{ secrets.DOCKER_USERNAME }}/rsshub:chromium-bundled # inline cache is enough + - name: Sign the published Docker image + if: ${{ github.event_name != 'pull_request' }} + env: + COSIGN_EXPERIMENTAL: 'true' + run: echo "${{ steps.meta-chromium-bundled.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build-and-push-chromium.outputs.digest }} + description: runs-on: ubuntu-latest needs: check-env if: needs.check-env.outputs.check-docker == 'true' + timeout-minutes: 5 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v3 + uses: peter-evans/dockerhub-description@v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/docker-test-cont.yml b/.github/workflows/docker-test-cont.yml new file mode 100644 index 00000000000000..be3c207be45aa4 --- /dev/null +++ b/.github/workflows/docker-test-cont.yml @@ -0,0 +1,119 @@ +name: PR - route test + +on: + workflow_run: + workflows: [PR - Docker build test] # open, reopen, synchronized, edited included + types: [completed] + +jobs: + testRoute: + name: Route test + runs-on: ubuntu-latest + permissions: + pull-requests: write + if: ${{ github.event.workflow_run.conclusion == 'success' }} # skip if unsuccessful + steps: + - uses: actions/checkout@v4 + + # https://github.com/orgs/community/discussions/25220 + - name: Search the PR that triggered this workflow + uses: potiuk/get-workflow-origin@v1_5 + id: source-run-info + with: + token: ${{ secrets.GITHUB_TOKEN }} + sourceRunId: ${{ github.event.workflow_run.id }} + + - name: Fetch PR data via GitHub API + uses: octokit/request-action@v2.x + id: pr-data + with: + route: GET /repos/{repo}/pulls/{number} + repo: ${{ github.repository }} + number: ${{ steps.source-run-info.outputs.pullRequestNumber }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Fetch affected routes + id: fetch-route + uses: actions/github-script@v7 + env: + PULL_REQUEST: ${{ steps.pr-data.outputs.data }} + with: + # by default, JSON format returned + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const PR = JSON.parse(process.env.PULL_REQUEST) + const body = PR.body + const number = PR.number + const sender = PR.user.login + const { default: identify } = await import('${{ github.workspace }}/scripts/workflow/test-route/identify.mjs') + return identify({ github, context, core }, body, number, sender) + + - name: Fetch Docker image + if: (env.TEST_CONTINUE) + uses: dawidd6/action-download-artifact@v7 + with: + workflow: ${{ github.event.workflow_run.workflow_id }} + run_id: ${{ github.event.workflow_run.id }} + name: docker-image + path: ../artifacts-${{ github.run_id }} + + - name: Import Docker image and set up Docker container + if: (env.TEST_CONTINUE) + working-directory: ../artifacts-${{ github.run_id }} + run: | + set -ex + zstd -d --stdout rsshub.tar.zst | docker load + docker run -d \ + --name rsshub \ + -e NODE_ENV=dev \ + -e LOGGER_LEVEL=debug \ + -e ALLOW_USER_HOTLINK_TEMPLATE=true \ + -e ALLOW_USER_SUPPLY_UNSAFE_DOMAIN=true \ + -p 1200:1200 \ + rsshub:latest + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + if: (env.TEST_CONTINUE) + with: + node-version: lts/* + cache: 'pnpm' + + - name: Install dependencies (pnpm) # require js-beautify + if: (env.TEST_CONTINUE) + run: pnpm i + + - name: Generate feedback + if: (env.TEST_CONTINUE) + id: generate-feedback + timeout-minutes: 10 + uses: actions/github-script@v7 + env: + TEST_BASEURL: http://localhost:1200 + TEST_ROUTES: ${{ steps.fetch-route.outputs.result }} + PULL_REQUEST: ${{ steps.pr-data.outputs.data }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const PR = JSON.parse(process.env.PULL_REQUEST) + const link = process.env.TEST_BASEURL + const routes = JSON.parse(process.env.TEST_ROUTES) + const number = PR.number + core.info(`${link}, ${routes}, ${number}`) + const { default: test } = await import('${{ github.workspace }}/scripts/workflow/test-route/test.mjs') + await test({ github, context, core }, link, routes, number) + + - name: Pull Request Labeler + if: ${{ failure() }} + uses: actions-cool/issues-helper@v3 + with: + actions: 'add-labels' + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ steps.source-run-info.outputs.pullRequestNumber }} + labels: 'Auto: Route Test Failed' + + - name: Print Docker container logs + if: (env.TEST_CONTINUE) + run: docker logs rsshub # logs/combined.log? Not so readable... diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index de7aa1966cb8d2..79144429814db9 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -1,5 +1,3 @@ -# name: '[docker] CI for build tests' -# https://github.community/t/215358 name: PR - Docker build test on: @@ -9,40 +7,43 @@ on: paths: - '.github/workflows/docker-test.yml' - 'lib/**' - - '!**/maintainer.js' - - '!**/radar.js' - - '!**/radar-rules.js' - 'Dockerfile' - 'package.json' - - 'yarn.lock' + - 'pnpm-lock.yaml' + types: [opened, reopened, synchronize, edited] # Please, always create a pull request instead of push to master. -permissions: - contents: read +concurrency: + group: docker-test-${{ github.ref_name }} + cancel-in-progress: true jobs: test: name: Docker build & tests + permissions: + pull-requests: write + attestations: write runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Docker Buildx # needed by `cache-from` - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: rsshub flavor: latest=true - name: Build Docker image - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: context: . - build-args: PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=0 # also test bundling Chromium + build-args: PUPPETEER_SKIP_DOWNLOAD=0 # also test bundling Chromium load: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} @@ -50,21 +51,25 @@ jobs: cache-from: | type=registry,ref=${{ secrets.DOCKER_USERNAME }}/rsshub:chromium-bundled type=gha,scope=docker-release - # ! build on amd64 is fast enough, and cache between PRs never hit, so never waste the 10GB cache limit ! - # cache-from: | - # type=gha,scope=docker-test - # type=gha,scope=docker-release - # cache-to: type=gha,mode=max,scope=docker-test + + - name: Pull Request Labeler + if: ${{ failure() }} + uses: actions-cool/issues-helper@v3 + with: + actions: 'add-labels' + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.pull_request.number }} + labels: 'Auto: Route Test Failed' - name: Test Docker image run: bash scripts/docker/test-docker.sh - name: Export Docker image - run: docker save rsshub:latest | gzip -1cf > rsshub.tar.gz + run: docker save rsshub:latest | zstdmt -o rsshub.tar.zst - name: Upload Docker image - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: docker-image - path: rsshub.tar.gz + path: rsshub.tar.zst retention-days: 1 diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 7c1362e583bdd9..a2ca9899fa5b32 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -1,27 +1,26 @@ -name: format +name: Format on: push: branches: - master -permissions: - contents: read - jobs: format: permissions: contents: write # for Git to git push name: Auto format runs-on: ubuntu-latest + timeout-minutes: 5 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 with: - node-version: 16 - cache: 'yarn' - - run: yarn install + node-version: lts/* + cache: 'pnpm' + - run: pnpm i - run: npm run format - name: Commit files run: | diff --git a/.github/workflows/issue-command.yml b/.github/workflows/issue-command.yml index 6c5bce954ce81c..b80c2b5ea2dfa4 100644 --- a/.github/workflows/issue-command.yml +++ b/.github/workflows/issue-command.yml @@ -4,62 +4,84 @@ on: issue_comment: types: [created] -permissions: - contents: read - jobs: rebase: name: Automatic Rebase if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase') && github.event.comment.author_association == 'COLLABORATOR' runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: write + pull-requests: write steps: - name: Checkout the latest code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Automatic Rebase - uses: cirrus-actions/rebase@1.7 + uses: cirrus-actions/rebase@1.8 env: - GITHUB_TOKEN: ${{ secrets.TOKEN_SUPER }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} self-assign: name: Self Assign if: ${{ !github.event.issue.pull_request && startsWith(github.event.comment.body, '/wip') }} runs-on: ubuntu-latest + timeout-minutes: 5 permissions: - contents: read issues: write steps: - - uses: bhermann/issue-volunteer@v0.1.20 + - uses: bdougie/take-action@v1.6.1 with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - assign_phrase: '/wip' - label_phase1: RSS proposal - label_phase2: RSS bug + token: ${{ secrets.GITHUB_TOKEN }} + trigger: '/wip' test-on-demand: name: Test route on demand if: startsWith(github.event.comment.body, '/test') runs-on: ubuntu-latest + timeout-minutes: 5 permissions: - contents: read + attestations: write issues: write + pull-requests: write steps: + - name: Fetch PR data (for PR) + if: github.event.issue.pull_request + uses: octokit/request-action@v2.x + id: pr-data + with: + route: GET /repos/{repo}/pulls/{number} + repo: ${{ github.repository }} + number: ${{ github.event.issue.number }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Checkout - uses: actions/checkout@v3 + if: ${{ !github.event.issue.pull_request }} + uses: actions/checkout@v4 + + - name: Checkout PR + if: github.event.issue.pull_request + uses: actions/checkout@v4 + with: + ref: ${{ fromJson(steps.pr-data.outputs.data).head.ref }} + + - name: Install pnpm + uses: pnpm/action-setup@v4 - - name: Use Node.js v16 - uses: actions/setup-node@v3 + - name: Use Node.js Active LTS + uses: actions/setup-node@v4 with: - node-version: 16 - cache: 'yarn' + node-version: lts/* + cache: 'pnpm' - - name: Install dependencies (yarn) - run: yarn + - name: Install dependencies (pnpm) + run: pnpm i && pnpm rb - name: Fetch affected routes - id: fetchRoute - uses: actions/github-script@v6 + id: fetch-route + uses: actions/github-script@v7 env: EVENT: ${{ toJson(github.event) }} with: @@ -68,22 +90,28 @@ jobs: const body = event.comment.body const number = event.issue.number const sender = event.comment.user.login - const script = require(`${process.env.GITHUB_WORKSPACE}/scripts/workflow/test-route/identify.js`) - return await script({ github, context, core }, body, number, sender) + const { default: identify } = await import('${{ github.workspace }}/scripts/workflow/test-route/identify.mjs') + return identify({ github, context, core }, body, number, sender) + + - name: Build RSSHub + if: env.TEST_CONTINUE + run: pnpm build - name: Start RSSHub if: env.TEST_CONTINUE - run: yarn start & + run: pnpm start & env: + ALLOW_USER_HOTLINK_TEMPLATE: true + ALLOW_USER_SUPPLY_UNSAFE_DOMAIN: true NODE_ENV: dev LOGGER_LEVEL: debug - name: Generate feedback if: env.TEST_CONTINUE - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: TEST_BASEURL: http://localhost:1200 - TEST_ROUTES: ${{ steps.fetchRoute.outputs.result }} + TEST_ROUTES: ${{ steps.fetch-route.outputs.result }} EVENT: ${{ toJson(github.event) }} with: script: | @@ -92,16 +120,15 @@ jobs: const routes = JSON.parse(process.env.TEST_ROUTES) const number = event.issue.number core.info(`${link}, ${routes}, ${number}`) - const got = require("got") - const script = require(`${process.env.GITHUB_WORKSPACE}/scripts/workflow/test-route/test.js`) - return await script({ github, context, core, got }, link, routes, number) + const { default: test } = await import('${{ github.workspace }}/scripts/workflow/test-route/test.mjs') + await test({ github, context, core }, link, routes, number) - name: Print logs - if: (env.TEST_CONTINUE) + if: env.TEST_CONTINUE run: cat ${{ github.workspace }}/logs/combined.log - name: Upload Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: logs path: logs diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000000000..4f317a38646bd1 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,83 @@ +name: Linter + +# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request +# pull_request includes [opened, reopened, synchronize] events by default +# 'edited' is required for title-lint +on: + push: {} + pull_request: + types: [opened, reopened, synchronize, edited] + pull_request_target: + types: [opened, reopened, synchronize, edited] + +jobs: + # eslint: + # name: ESLint + # if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }} + # runs-on: ubuntu-latest + # timeout-minutes: 5 + # steps: + # - uses: actions/checkout@v4 + # - uses: pnpm/action-setup@v4 + # with: + # version: 9 + # - uses: actions/setup-node@v4 + # with: + # node-version: lts/* + # cache: 'pnpm' + # - run: pnpm i + # - name: Lint + # run: pnpm run lint + +# https://github.com/actions/starter-workflows/blob/main/code-scanning/eslint.yml + eslint-warning: + name: Lint + if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + security-events: write + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: 'pnpm' + - run: pnpm i + - name: Lint + run: pnpm run lint + --format @microsoft/eslint-formatter-sarif + --output-file eslint-results.sarif + continue-on-error: true + - name: Upload analysis results to GitHub + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: eslint-results.sarif + wait-for-processing: true + +# https://github.com/amannn/action-semantic-pull-request + title-lint: + if: ${{ github.event_name == 'pull_request_target' && github.repository == 'DIYgod/RSSHub' }} + name: Validate PR title + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ignoreLabels: | + dependencies + wip: true + + labeler: + name: Pull Request Labeler + if: ${{ github.event_name == 'pull_request_target' && github.actor != 'dependabot[bot]' && github.repository == 'DIYgod/RSSHub' }} + permissions: + pull-requests: write + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/labeler@v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 5703d0e5ab05cc..bd6666ae3af8f8 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -1,33 +1,42 @@ -name: publish +name: npm Publish on: push: branches: - master paths: + - '.github/workflows/npm-publish.yml' - 'lib/**' permissions: - contents: read + id-token: write jobs: build: name: npm publish if: github.repository == 'DIYgod/RSSHub' runs-on: ubuntu-latest + timeout-minutes: 5 + env: + HUSKY: 0 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 with: - node-version: 16 - cache: 'yarn' + node-version: lts/* + cache: 'pnpm' registry-url: 'https://registry.npmjs.org' + - name: Install dependencies (pnpm) + run: pnpm i + - name: Run postinstall script for dependencies + run: pnpm rb - name: Release run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action" npx version-from-git --allow-same-version --template 'master.short' - name: Publish to npmjs - run: npm publish + run: pnpm publish --provenance --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/pr-deploy-route-test.yml b/.github/workflows/pr-deploy-route-test.yml deleted file mode 100644 index 797c65868784fb..00000000000000 --- a/.github/workflows/pr-deploy-route-test.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: PR - route test -on: - workflow_run: - workflows: [ PR - Docker build test ] # open, reopen, synchronized included - types: [ completed ] - -jobs: - testRoute: - name: Route test - runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'success' }} # skip if unsuccessful - steps: - - uses: actions/checkout@v3 - - # https://github.community/t/154682 - - name: Search the PR that triggered this workflow - uses: potiuk/get-workflow-origin@v1_3 - id: source-run-info - with: - token: ${{ secrets.GITHUB_TOKEN }} - sourceRunId: ${{ github.event.workflow_run.id }} - - - name: Fetch PR data via GitHub API - uses: octokit/request-action@v2.x - id: pr-data - with: - route: GET /repos/{repo}/pulls/{number} - repo: ${{ github.repository }} - number: ${{ steps.source-run-info.outputs.pullRequestNumber }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Fetch affected routes - id: fetchRoute - uses: actions/github-script@v6 - env: - PULL_REQUEST: ${{ steps.pr-data.outputs.data }} - with: - # by default, JSON format returned - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const PR = JSON.parse(process.env.PULL_REQUEST) - const body = PR.body - const number = PR.number - const sender = PR.user.login - const script = require(`${process.env.GITHUB_WORKSPACE}/scripts/workflow/test-route/identify.js`) - return await script({ github, context, core }, body, number, sender) - - - name: Fetch Docker image - if: (env.TEST_CONTINUE) - uses: dawidd6/action-download-artifact@v2 - with: - workflow: ${{ github.event.workflow_run.workflow_id }} - run_id: ${{ github.event.workflow_run.id }} - - - name: Import Docker image and set up Docker container - if: (env.TEST_CONTINUE) - run: | - set -ex - gzip -cvd docker-image/rsshub.tar.gz | docker load - docker run -d \ - --name rsshub \ - -e NODE_ENV=dev \ - -e LOGGER_LEVEL=debug \ - -p 1200:1200 \ - rsshub:latest - - - uses: actions/setup-node@v3 # just need its cache - if: (env.TEST_CONTINUE) - with: - node-version: 16 - cache: 'yarn' - - - name: Install dependencies (yarn) # `got` needed since `github.request` disallows HTTP requests - if: (env.TEST_CONTINUE) - run: yarn - - - name: Generate feedback - if: (env.TEST_CONTINUE) - uses: actions/github-script@v6 - env: - TEST_BASEURL: http://localhost:1200 - TEST_ROUTES: ${{ steps.fetchRoute.outputs.result }} - PULL_REQUEST: ${{ steps.pr-data.outputs.data }} - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const PR = JSON.parse(process.env.PULL_REQUEST) - const link = process.env.TEST_BASEURL - const routes = JSON.parse(process.env.TEST_ROUTES) - const number = PR.number - core.info(`${link}, ${routes}, ${number}`) - const got = require("got"); - const script = require(`${process.env.GITHUB_WORKSPACE}/scripts/workflow/test-route/test.js`) - return await script({ github, context, core, got }, link, routes, number) - - - name: Print Docker container logs - if: (env.TEST_CONTINUE) - run: docker logs rsshub # logs/combined.log? Not so readable... diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml deleted file mode 100644 index fe1bca16da50d0..00000000000000 --- a/.github/workflows/pr-lint.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Linter - -on: [push, pull_request, pull_request_target] - -jobs: - linter: - name: Lint - if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 16 - cache: 'yarn' - - run: yarn - - name: Lint - run: yarn lint - reviewdog: - name: reviewdog - if: ${{ github.event_name == 'pull_request' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 16 - cache: 'yarn' - - run: yarn - - name: Lint - uses: reviewdog/action-eslint@v1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - titleLint: - if: ${{ github.event_name == 'pull_request_target' }} - name: Validate PR title - runs-on: ubuntu-latest - steps: - - uses: amannn/action-semantic-pull-request@v4 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ignoreLabels: | - dependencies - wip: true - labeler: - name: Pull Request Labeler - if: ${{ github.event_name == 'pull_request_target' }} - permissions: - contents: read - pull-requests: write - runs-on: ubuntu-latest - steps: - - uses: actions/labeler@v4 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/scrape.yml b/.github/workflows/scrape.yml deleted file mode 100644 index cca7122b3e215e..00000000000000 --- a/.github/workflows/scrape.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Update meilisearch index - -on: - push: - branches: - - master - paths: - - '.github/workflows/scrape.yml' - - 'docs/**' - workflow_dispatch: ~ - schedule: - - cron: '44 1 * * 1' - -jobs: - scrape-docs: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Use Node.js v16 - uses: actions/setup-node@v3 - with: - node-version: 16 - cache: 'yarn' - - name: Install dependencies (yarn) - run: yarn - - name: Pull image - run: docker pull getmeili/docs-scraper - - name: Wait for Netlify to finish - run: sleep 2m - if: github.event_name == 'push' - - name: Run docs-scraper - env: - HOST_URL: ${{ secrets.MEILISEARCH_HOST_URL }} - API_KEY: ${{ secrets.MEILISEARCH_API_KEY }} - CONFIG_FILE_PATH: ${{ github.workspace }}/scripts/docs-scraper/docs.rsshub.app.json - run: | - docker run -t --rm \ - -e MEILISEARCH_HOST_URL=$HOST_URL \ - -e MEILISEARCH_API_KEY=$API_KEY \ - -v $CONFIG_FILE_PATH:/docs-scraper/config.json \ - getmeili/docs-scraper pipenv run ./docs_scraper config.json diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 00000000000000..5fb469de005d95 --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,33 @@ +name: Semgrep + +# https://semgrep.dev/docs/semgrep-ci/sample-ci-configs/#sample-github-actions-configuration-file +on: + pull_request_target: + branches: + - master + push: + branches: + - master + schedule: + # random HH:MM to avoid a load spike on GitHub Actions at 00:00 + - cron: 21 20 * * * + +jobs: + semgrep: + name: Scan + runs-on: ubuntu-latest + container: + image: returntocorp/semgrep + if: (github.triggering_actor != 'dependabot[bot]') + permissions: + security-events: write + steps: + - uses: actions/checkout@v4 + - run: semgrep ci --sarif > semgrep.sarif + env: + SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} + - name: Upload SARIF file for GitHub Advanced Security Dashboard + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: semgrep.sarif + if: always() diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000000000..c11a215094233a --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,27 @@ +name: 'Close stale issues and PRs' + +on: + schedule: + - cron: '31 23 * * *' + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + # Don't stale issues + days-before-issue-stale: -1 + days-before-pr-stale: 23 + days-before-close: 7 + stale-pr-message: > + This PR is stale because it has been opened for more than 3 weeks with no activity. Comment or this will be closed in 7 days. + close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' + close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.' + exempt-issue-labels: 'dependencies,wait for upstream' + exempt-pr-labels: 'dependencies,wait for upstream' + any-of-issue-labels: 'more data required' diff --git a/.github/workflows/test-full-routes.yml b/.github/workflows/test-full-routes.yml new file mode 100644 index 00000000000000..ce46651922ba53 --- /dev/null +++ b/.github/workflows/test-full-routes.yml @@ -0,0 +1,39 @@ +name: Build assets (Full Routes Test Result) + +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' + +jobs: + build: + runs-on: ubuntu-latest + name: Build assets + timeout-minutes: 120 + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Use Node.js Active LTS + uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: 'pnpm' + - name: Install dependencies (yarn) + run: pnpm i + - name: Build assets + run: pnpm build + - name: Build full routes test result + continue-on-error: true + run: pnpm vitest:fullroutes + - name: Deploy + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./assets + user_name: 'github-actions[bot]' + user_email: '41898282+github-actions[bot]@users.noreply.github.com' + keep_files: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d74653da93b62d..f335abdebb7725 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,26 +1,23 @@ -name: test +name: Test on: push: branches-ignore: - 'dependabot/**' paths: - - 'test/**' - 'lib/**' - - '!**/maintainer.js' - - '!**/radar.js' - - '!**/radar-rules.js' - 'package.json' - - 'yarn.lock' + - 'pnpm-lock.yaml' - '.github/workflows/test.yml' - pull_request: ~ + pull_request: {} permissions: - contents: read + checks: write jobs: - jest: + vitest: runs-on: ubuntu-latest + timeout-minutes: 10 services: redis: image: redis @@ -28,59 +25,75 @@ jobs: - 6379/tcp options: --entrypoint redis-server strategy: + fail-fast: false matrix: - node-version: [ 14, 16 ] - name: Jest on Node ${{ matrix.node-version }} + node-version: [ latest, lts/*, lts/-1 ] + name: Vitest on Node ${{ matrix.node-version }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - cache: 'yarn' - - name: Install dependencies (yarn) - run: yarn + cache: 'pnpm' + - name: Install dependencies (pnpm) + run: pnpm i + - name: Run postinstall script for dependencies + run: pnpm rb + - name: Build routes + run: pnpm build - name: Test all and generate coverage - run: npm run jest:coverage + run: pnpm run vitest:coverage --reporter=github-actions env: REDIS_URL: redis://localhost:${{ job.services.redis.ports[6379] }}/ - name: Upload coverage to Codecov - if: ${{ matrix.node-version == '16' }} - uses: codecov/codecov-action@v3 + if: ${{ matrix.node-version == 'lts/*' }} + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos as documented, but seems broken puppeteer: runs-on: ubuntu-latest + timeout-minutes: 10 strategy: fail-fast: false matrix: - node-version: [ 14, 16 ] + node-version: [ latest, lts/*, lts/-1 ] chromium: - name: bundled Chromium dependency: '' - environment: '{}' + environment: '{ "PUPPETEER_SKIP_DOWNLOAD": "0" }' - name: Chromium from Ubuntu dependency: chromium-browser - environment: '{ "CHROMIUM_EXECUTABLE_PATH": "chromium-browser" }' + environment: '{ "PUPPETEER_SKIP_DOWNLOAD": "1" }' - name: Chrome from Google dependency: google-chrome-stable - environment: '{ "CHROMIUM_EXECUTABLE_PATH": "google-chrome-stable" }' - name: Jest puppeteer on Node ${{ matrix.node-version }} with ${{ matrix.chromium.name }} + environment: '{ "PUPPETEER_SKIP_DOWNLOAD": "1" }' + name: Vitest puppeteer on Node ${{ matrix.node-version }} with ${{ matrix.chromium.name }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - cache: 'yarn' - - name: Install dependencies (yarn) - run: yarn + cache: 'pnpm' + - name: Install dependencies (pnpm) + run: pnpm i + env: ${{ fromJSON(matrix.chromium.environment) }} + - name: Run postinstall script for dependencies + run: pnpm rb + env: ${{ fromJSON(matrix.chromium.environment) }} + - name: Build routes + run: pnpm build + env: ${{ fromJSON(matrix.chromium.environment) }} - name: Install Chromium if: ${{ matrix.chromium.dependency != '' }} - # Chromium from Ubuntu is too old (85), but can still pass the tests - # That's not really a problem since Chromium-bundled Docker image is based on Debian bullseye, - # which updates Chromium frequently, and only on arm/arm64 the image needs Chromium from Debian. + # 'chromium-browser' from Ubuntu APT repo is a dummy package. Its version (85.0.4183.83) means + # nothing since it calls Snap (disgusting!) to install Chromium, which should be up-to-date. + # That's not really a problem since the Chromium-bundled Docker image is based on Debian bookworm, + # which provides up-to-date native packages. run: | - set -ex + set -eux curl -s "https://dl.google.com/linux/linux_signing_key.pub" | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/google-chrome.gpg > /dev/null echo "deb [arch=amd64] https://dl.google.com/linux/chrome/deb/ stable main" | @@ -88,44 +101,41 @@ jobs: sudo apt-get update sudo apt-get install -yq --no-install-recommends ${{ matrix.chromium.dependency }} - name: Test puppeteer - run: npm run jest puppeteer + run: | + set -eux + export CHROMIUM_EXECUTABLE_PATH="$(which ${{ matrix.chromium.dependency }})" + pnpm run vitest puppeteer env: ${{ fromJSON(matrix.chromium.environment) }} - docs: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [ 14, 16 ] - name: Build docs on Node ${{ matrix.node-version }} - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - cache: 'yarn' - - run: yarn - - name: Build docs - run: npm run docs:build - all: runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + attestations: write strategy: + fail-fast: false matrix: - node-version: [ 14, 16 ] + node-version: [ 23, 22, 20 ] name: Build radar and maintainer on Node ${{ matrix.node-version }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - cache: 'yarn' - - run: yarn + cache: 'pnpm' + - run: pnpm i - name: Build radar and maintainer - run: npm run build:all + run: npm run build + - name: Upload assets + uses: actions/upload-artifact@v4 + with: + name: generated-assets-${{ matrix.node-version }} + path: assets/build/ automerge: - if: github.actor == 'dependabot[bot]' && github.event_name == 'pull_request' - needs: [ jest, puppeteer, docs, all ] + if: github.triggering_actor == 'dependabot[bot]' && github.event_name == 'pull_request' + needs: [ vitest, puppeteer, all ] runs-on: ubuntu-latest permissions: pull-requests: write diff --git a/.github/workflows/yarn-lock-changes.yml b/.github/workflows/yarn-lock-changes.yml deleted file mode 100644 index b948f11f7e2c12..00000000000000 --- a/.github/workflows/yarn-lock-changes.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Yarn Lock Changes -on: - pull_request: - paths: - - '.github/workflows/yarn-lock-changes.yml' - - 'yarn.lock' - -jobs: - yarn_lock_changes: - runs-on: ubuntu-latest - # Permission overwrite is required for Dependabot PRs, see https://github.com/marketplace/actions/yarn-lock-changes#-common-issues. - permissions: - pull-requests: write - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Yarn Lock Changes - uses: Simek/yarn-lock-changes@v0.11.1 - with: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index b7ddcd024fde81..368a4c547ba8e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,32 @@ -node_modules -npm-debug.log -error.log -combined.log -.vscode -.idea .DS_Store -docs/.vuepress/dist -lib/config/app.json -lib/config/config.js -yarn-error.log -tmp -*.swp -*.iml -coverage -.env -app-minimal/ -dump.rdb -Session.vim - +.env* +.eslintcache +.idea +.log .now .vercel +.vscode +.yarn +.yarnrc.yml +.pnp* + +*.swp +*.iml +app-minimal/ assets/build/ +coverage +docs/.vuepress/dist +node_modules +tmp +dist + +Session.vim +combined.log +dump.rdb +error.log +npm-debug.log package-lock.json +# pnpm-lock.yaml +yarn.lock +yarn-error.log diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 00000000000000..0ee2c93f22e2c0 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,38 @@ +image: gitpod/workspace-node-lts + +ports: + - port: 1200 + onOpen: notify + visibility: public + - port: 3000 + onOpen: notify + visibility: public + +tasks: + - name: deps + before: | + sudo apt update + sudo apt install -y ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 libcairo2 libcups2 libdbus-1-3 libexpat1 libgbm1 libglib2.0-0 libnspr4 libnss3 libpango-1.0-0 libx11-6 libxcb1 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxkbcommon0 libxrandr2 wget xdg-util + sudo apt install -y redis-server + init: pnpm i && pnpm rb + - name: app + command: pnpm run dev + openMode: tab-after + # - name: docs + # command: | + # cd website + # pnpm run start + # openMode: tab-after + +vscode: + extensions: + - cweijan.vscode-database-client2 # you may need to rollback to v5.3.1 or below in **VS Code Desktop** + - dbaeumer.vscode-eslint + - eamodio.gitlens + - EditorConfig.EditorConfig + - esbenp.prettier-vscode + - deepscan.vscode-deepscan + - sonarsource.sonarlint-vscode + # - VASubasRaj.flashpost not available on Open VSX, Thunder Client is paywalled in WSL/Codespaces/SSH > 2.30.0 + - unifiedjs.vscode-mdx + # - ZihanLi.at-helper not available on Open VSX diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000000000..c27d8893a99490 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +lint-staged diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000000000..74538ab74e5dd9 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +package-lock=true +package-manager-strict=false diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index b009dfb9d9f98f..00000000000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -lts/* diff --git a/.prettierignore b/.prettierignore index b4a96659f02487..b6b684c5ac07b0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,7 +1,4 @@ -package.json -docs/.vuepress/dist -package-lock.json -.github/*.md -renovate.json -coverage -.vscode/ +lib/routes-deprecated +lib/router.js +babel.config.js +scripts/docker/minify-docker.js diff --git a/.puppeteerrc.cjs b/.puppeteerrc.cjs new file mode 100644 index 00000000000000..a4e6d37234ef19 --- /dev/null +++ b/.puppeteerrc.cjs @@ -0,0 +1,9 @@ +const path = require('path'); + +/** + * @type {import("puppeteer").Configuration} + */ +module.exports = { + // Changes the cache location for Puppeteer. + cacheDirectory: path.join(__dirname, 'node_modules', '.cache', 'puppeteer'), +}; diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index c63af734ae66fa..e452526ecf3d55 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -39,7 +39,7 @@ This Code of Conduct applies within all community spaces, and also applies when ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at i@diygod.me. All complaints will be reviewed and investigated promptly and fairly. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at . All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. @@ -74,11 +74,11 @@ Community leaders will follow these Community Impact Guidelines in determining t ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, -available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. +available at . Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. +. Translations are available at . diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e3c4c56d17dfa6..273aa2c8b304c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1 @@ -## 请参见[参与我们](https://docs.rsshub.app/joinus/quick-start.html) - -## Please refer to [Join Us](https://docs.rsshub.app/en/joinus/quick-start.html) +## Please refer to [Join Us](https://docs.rsshub.app/joinus/) diff --git a/Dockerfile b/Dockerfile index 1305fc78ee34c9..9089f0ca955d48 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16-bullseye as dep-builder +FROM node:22-bookworm AS dep-builder # Here we use the non-slim image to provide build-time deps (compilers and python), thus no need to install later. # This effectively speeds up qemu-based cross-build. @@ -6,137 +6,135 @@ WORKDIR /app # place ARG statement before RUN statement which need it to avoid cache miss ARG USE_CHINA_NPM_REGISTRY=0 -ARG TARGETPLATFORM RUN \ set -ex && \ + corepack enable pnpm && \ if [ "$USE_CHINA_NPM_REGISTRY" = 1 ]; then \ echo 'use npm mirror' && \ npm config set registry https://registry.npmmirror.com && \ - yarn config set registry https://registry.npmmirror.com ; \ + yarn config set registry https://registry.npmmirror.com && \ + pnpm config set registry https://registry.npmmirror.com ; \ fi; -COPY ./yarn.lock /app/ +COPY ./tsconfig.json /app/ +COPY ./pnpm-lock.yaml /app/ COPY ./package.json /app/ -# required for building canvas in arm64 and arm/v7 (introduce in #10954) -RUN \ - set -ex && \ - if [ "$TARGETPLATFORM" != "linux/amd64" ]; then \ - apt-get update && \ - apt-get install -yq --no-install-recommends \ - libpango1.0-dev ; \ - fi; - # lazy install Chromium to avoid cache miss, only install production dependencies to minimize the image size RUN \ set -ex && \ - export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true && \ - yarn install --production --frozen-lockfile --network-timeout 1000000 && \ - yarn cache clean + export PUPPETEER_SKIP_DOWNLOAD=true && \ + pnpm install --frozen-lockfile && \ + pnpm rb # --------------------------------------------------------------------------------------------------------------------- -FROM debian:bullseye-slim as dep-version-parser +FROM debian:bookworm-slim AS dep-version-parser # This stage is necessary to limit the cache miss scope. # With this stage, any modification to package.json won't break the build cache of the next two stages as long as the # version unchanged. -# node:16-bullseye-slim is based on debian:bullseye-slim so this stage would not cause any additional download. +# node:22-bookworm-slim is based on debian:bookworm-slim so this stage would not cause any additional download. WORKDIR /ver COPY ./package.json /app/ RUN \ set -ex && \ - grep -Po '(?<="puppeteer": ")[^\s"]*(?=")' /app/package.json | tee /ver/.puppeteer_version && \ - grep -Po '(?<="@vercel/nft": ")[^\s"]*(?=")' /app/package.json | tee /ver/.nft_version && \ - grep -Po '(?<="fs-extra": ")[^\s"]*(?=")' /app/package.json | tee /ver/.fs_extra_version + grep -Po '(?<="puppeteer": ")[^\s"]*(?=")' /app/package.json | tee /ver/.puppeteer_version + # grep -Po '(?<="@vercel/nft": ")[^\s"]*(?=")' /app/package.json | tee /ver/.nft_version && \ + # grep -Po '(?<="fs-extra": ")[^\s"]*(?=")' /app/package.json | tee /ver/.fs_extra_version # --------------------------------------------------------------------------------------------------------------------- -FROM node:16-bullseye-slim as docker-minifier +FROM node:22-bookworm-slim AS docker-minifier # The stage is used to further reduce the image size by removing unused files. -WORKDIR /minifier -COPY --from=dep-version-parser /ver/* /minifier/ - -ARG USE_CHINA_NPM_REGISTRY=0 -RUN \ - set -ex && \ - if [ "$USE_CHINA_NPM_REGISTRY" = 1 ]; then \ - npm config set registry https://registry.npmmirror.com && \ - yarn config set registry https://registry.npmmirror.com ; \ - fi; \ - yarn add @vercel/nft@$(cat .nft_version) fs-extra@$(cat .fs_extra_version) && \ - yarn cache clean +WORKDIR /app +# COPY --from=dep-version-parser /ver/* /minifier/ + +# ARG USE_CHINA_NPM_REGISTRY=0 +# RUN \ +# set -ex && \ +# if [ "$USE_CHINA_NPM_REGISTRY" = 1 ]; then \ +# npm config set registry https://registry.npmmirror.com && \ +# yarn config set registry https://registry.npmmirror.com && \ +# pnpm config set registry https://registry.npmmirror.com ; \ +# fi; \ +# corepack enable pnpm && \ +# pnpm add @vercel/nft@$(cat .nft_version) fs-extra@$(cat .fs_extra_version) --save-prod COPY . /app COPY --from=dep-builder /app /app RUN \ set -ex && \ - cp /app/scripts/docker/minify-docker.js /minifier/ && \ - export PROJECT_ROOT=/app && \ - node /minifier/minify-docker.js && \ - rm -rf /app/node_modules /app/scripts && \ - mv /app/app-minimal/node_modules /app/ && \ - rm -rf /app/app-minimal && \ + # cp /app/scripts/docker/minify-docker.js /minifier/ && \ + # export PROJECT_ROOT=/app && \ + # node /minifier/minify-docker.js && \ + # rm -rf /app/node_modules /app/scripts && \ + # mv /app/app-minimal/node_modules /app/ && \ + # rm -rf /app/app-minimal && \ + npm run build && \ ls -la /app && \ du -hd1 /app # --------------------------------------------------------------------------------------------------------------------- -FROM node:16-bullseye-slim as chromium-downloader +FROM node:22-bookworm-slim AS chromium-downloader # This stage is necessary to improve build concurrency and minimize the image size. # Yeah, downloading Chromium never needs those dependencies below. WORKDIR /app +COPY ./.puppeteerrc.cjs /app/ COPY --from=dep-version-parser /ver/.puppeteer_version /app/.puppeteer_version ARG TARGETPLATFORM ARG USE_CHINA_NPM_REGISTRY=0 -ARG PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 +ARG PUPPETEER_SKIP_DOWNLOAD=1 # The official recommended way to use Puppeteer on x86(_64) is to use the bundled Chromium from Puppeteer: -# https://github.com/puppeteer/puppeteer#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy +# https://pptr.dev/faq#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy RUN \ set -ex ; \ - if [ "$PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" = 0 ] && [ "$TARGETPLATFORM" = 'linux/amd64' ]; then \ + if [ "$PUPPETEER_SKIP_DOWNLOAD" = 0 ] && [ "$TARGETPLATFORM" = 'linux/amd64' ]; then \ if [ "$USE_CHINA_NPM_REGISTRY" = 1 ]; then \ npm config set registry https://registry.npmmirror.com && \ - yarn config set registry https://registry.npmmirror.com ; \ + yarn config set registry https://registry.npmmirror.com && \ + pnpm config set registry https://registry.npmmirror.com ; \ fi; \ echo 'Downloading Chromium...' && \ - unset PUPPETEER_SKIP_CHROMIUM_DOWNLOAD && \ - yarn add puppeteer@$(cat /app/.puppeteer_version) && \ - yarn cache clean ; \ + unset PUPPETEER_SKIP_DOWNLOAD && \ + corepack enable pnpm && \ + pnpm add puppeteer@$(cat /app/.puppeteer_version) --save-prod && \ + pnpm rb ; \ else \ - mkdir -p /app/node_modules/puppeteer ; \ + mkdir -p /app/node_modules/.cache/puppeteer ; \ fi; # --------------------------------------------------------------------------------------------------------------------- -FROM node:16-bullseye-slim as app +FROM node:22-bookworm-slim AS app LABEL org.opencontainers.image.authors="https://github.com/DIYgod/RSSHub" -ENV NODE_ENV production -ENV TZ Asia/Shanghai +ENV NODE_ENV=production +ENV TZ=Asia/Shanghai WORKDIR /app # install deps first to avoid cache miss or disturbing buildkit to build concurrently ARG TARGETPLATFORM -ARG PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 -# https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#chrome-headless-doesnt-launch-on-unix +ARG PUPPETEER_SKIP_DOWNLOAD=1 +# https://pptr.dev/troubleshooting#chrome-headless-doesnt-launch-on-unix # https://github.com/puppeteer/puppeteer/issues/7822 -# https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html#noteworthy-obsolete-packages +# https://www.debian.org/releases/bookworm/amd64/release-notes/ch-information.en.html#noteworthy-obsolete-packages # The official recommended way to use Puppeteer on arm/arm64 is to install Chromium from the distribution repositories: -# https://github.com/puppeteer/puppeteer/blob/94cb08c85955c0688d12b6ed10e61a4581a01280/src/node/BrowserFetcher.ts#L116-L119 +# https://github.com/puppeteer/puppeteer/blob/07391bbf5feaf85c191e1aa8aa78138dce84008d/packages/puppeteer-core/src/node/BrowserFetcher.ts#L128-L131 RUN \ set -ex && \ apt-get update && \ apt-get install -yq --no-install-recommends \ - dumb-init \ + dumb-init git curl \ ; \ - if [ "$PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" = 0 ]; then \ + if [ "$PUPPETEER_SKIP_DOWNLOAD" = 0 ]; then \ if [ "$TARGETPLATFORM" = 'linux/amd64' ]; then \ apt-get install -yq --no-install-recommends \ ca-certificates fonts-liberation wget xdg-utils \ @@ -148,20 +146,18 @@ RUN \ apt-get install -yq --no-install-recommends \ chromium \ && \ - echo 'CHROMIUM_EXECUTABLE_PATH=chromium' | tee /app/.env ; \ + echo "CHROMIUM_EXECUTABLE_PATH=$(which chromium)" | tee /app/.env ; \ fi; \ fi; \ rm -rf /var/lib/apt/lists/* -COPY --from=chromium-downloader /app/node_modules/puppeteer /app/node_modules/puppeteer +COPY --from=chromium-downloader /app/node_modules/.cache/puppeteer /app/node_modules/.cache/puppeteer -# if grep matches nothing then it will exit with 1, thus, we cannot `set -e` here RUN \ - set -x && \ - if [ "$PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" = 0 ] && [ "$TARGETPLATFORM" = 'linux/amd64' ]; then \ + set -ex && \ + if [ "$PUPPETEER_SKIP_DOWNLOAD" = 0 ] && [ "$TARGETPLATFORM" = 'linux/amd64' ]; then \ echo 'Verifying Chromium installation...' && \ - ldd $(find /app/node_modules/puppeteer/ -name chrome) | grep "not found" ; \ - if [ "$?" = 0 ]; then \ + if ldd $(find /app/node_modules/.cache/puppeteer/ -name chrome -type f) | grep "not found"; then \ echo "!!! Chromium has unmet shared libs !!!" && \ exit 1 ; \ else \ @@ -187,7 +183,7 @@ CMD ["npm", "run", "start"] # apt-file \ # && \ # apt-file update && \ -# ldd $(find /app/node_modules/puppeteer/ -name chrome) | grep -Po "\S+(?= => not found)" | \ +# ldd $(find /app/node_modules/.cache/puppeteer/ -name chrome -type f) | grep -Po "\S+(?= => not found)" | \ # sed 's/\./\\./g' | awk '{print $1"$"}' | apt-file search -xlf - | grep ^lib | \ # xargs -d '\n' -- \ # apt-get install -yq --no-install-recommends \ diff --git a/Procfile b/Procfile deleted file mode 100644 index a9472b4a38e23e..00000000000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: node lib/index.js diff --git a/README.md b/README.md index 8c3e037eb86af2..defee8667f8dac 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,60 @@

-RSSHub +RSSHub

RSSHub

-> 🍰 Everything is RSSible +> 🧡 Everything is RSSible -[![telegram](https://img.shields.io/badge/chat-telegram-brightgreen.svg?logo=telegram&style=flat-square)](https://t.me/rsshub) -[![npm publish](https://img.shields.io/github/workflow/status/DIYgod/RSSHub/publish/master?label=npm%20publish&logo=npm&style=flat-square)](https://www.npmjs.com/package/rsshub) -[![docker publish](https://img.shields.io/github/workflow/status/DIYgod/RSSHub/%5Bdocker%5D%20CI%20for%20releases/master?label=docker%20publish&logo=docker&style=flat-square)](https://hub.docker.com/r/diygod/rsshub) -[![test](https://img.shields.io/github/workflow/status/DIYgod/RSSHub/test/master?label=test&logo=github&style=flat-square)](https://github.com/DIYgod/RSSHub/actions/workflows/test.yml?query=event%3Apush+branch%3Amaster) +[![](https://img.shields.io/badge/dynamic/json?url=https://rsshub-analytics.diygod.workers.dev/&query=requests&color=F38020&label=requests&logo=cloudflare&style=flat-square&suffix=/month)](https://rsshub.app) +[![docker publish](https://img.shields.io/docker/pulls/diygod/rsshub?label=docker%20pulls&logo=docker&style=flat-square)](https://hub.docker.com/r/diygod/rsshub) +[![npm publish](https://img.shields.io/npm/dt/rsshub?label=npm%20downloads&logo=npm&style=flat-square)](https://www.npmjs.com/package/rsshub) +[![test](https://img.shields.io/github/actions/workflow/status/DIYgod/RSSHub/test.yml?branch=master&label=test&logo=github&style=flat-square)](https://github.com/DIYgod/RSSHub/actions/workflows/test.yml?query=event%3Apush+branch%3Amaster) [![Test coverage](https://img.shields.io/codecov/c/github/DIYgod/RSSHub.svg?style=flat-square&logo=codecov)](https://app.codecov.io/gh/DIYgod/RSSHub/branch/master) +[![Visitors](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2FDIYgod%2FRSSHub&count_bg=%23FF752E&title_bg=%23555555&icon=rss.svg&icon_color=%23FF752E&title=RSS+lovers&edge_flat=true)](https://github.com/DIYgod/RSSHub) + +[![Telegram group](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.swo.moe%2Fstats%2Ftelegram%2Frsshub&query=count&color=2CA5E0&label=Telegram%20Group&logo=telegram&cacheSeconds=3600&style=flat-square)](https://t.me/rsshub) [![Telegram channel](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.swo.moe%2Fstats%2Ftelegram%2FawesomeRSSHub&query=count&color=2CA5E0&label=Telegram%20Channel&logo=telegram&cacheSeconds=3600&style=flat-square)](https://t.me/awesomeRSSHub) [![X (Twitter)](https://img.shields.io/badge/any_text-Follow-blue?color=2CA5E0&label=Twitter&logo=X&cacheSeconds=3600&style=flat-square)](https://x.com/intent/follow?screen_name=_RSSHub) ## Introduction -RSSHub is an open source, easy to use, and extensible RSS feed generator. It's capable of generating RSS feeds from pretty much everything. +RSSHub is the world's largest RSS network, consisting of over 5,000 global instances. RSSHub delivers millions of contents aggregated from all kinds of sources, our vibrant open source community is ensuring the deliver of RSSHub's new routes, new features and bug fixes. -RSSHub can be used with browser extension [RSSHub Radar](https://github.com/DIYgod/RSSHub-Radar) and mobile auxiliary app [RSSBud](https://github.com/Cay-Zhang/RSSBud) (iOS) and [RSSAid](https://github.com/LeetaoGoooo/RSSAid) (Android) - -[English docs](https://docs.rsshub.app/en) | [Telegram Group](https://t.me/rsshub) | [Telegram Channel](https://t.me/awesomeRSSHub) - ---- - -RSSHub 是一个开源、简单易用、易于扩展的 RSS 生成器,可以给任何奇奇怪怪的内容生成 RSS 订阅源。RSSHub 借助于开源社区的力量快速发展中,目前已适配数百家网站的上千项内容 - -可以配合浏览器扩展 [RSSHub Radar](https://github.com/DIYgod/RSSHub-Radar) 和 移动端辅助 App [RSSBud](https://github.com/Cay-Zhang/RSSBud) (iOS) 与 [RSSAid](https://github.com/LeetaoGoooo/RSSAid) (Android) 食用 - -[中文文档](https://docs.rsshub.app) | [Telegram 群](https://t.me/rsshub) | [Telegram 频道](https://t.me/awesomeRSSHub) - -## Special Thanks - -### Special Sponsors - -
- -
- -### Sponsors - -[Sayori Studio](https://t.me/SayoriStudio) . [Sion Kazama](https://blog.sion.moe) . [琚致远](https://wineso.me/) . [Rolly RSS 阅读器](https://www.coolapk.com/apk/239500) . [mokeyjay](https://www.mokeyjay.com/) . [萌开源联盟](https://www.moeunion.com) . [hooke007](https://github.com/hooke007/MPV_lazy) . [feeds.pub](https://feeds.pub) . [KINGX@安全引擎](http://cve.today/) - -[![](https://opencollective.com/static/images/become_sponsor.svg)](https://docs.rsshub.app/en/support/) - -### Contributors - -[![](https://opencollective.com/RSSHub/contributors.svg?width=890)](https://github.com/DIYgod/RSSHub/graphs/contributors) - -Logo designer [sheldonrrr](https://dribbble.com/sheldonrrr) - -### Backers - -        +[Documentation](https://docs.rsshub.app) | [Telegram Group](https://t.me/rsshub) | [Telegram Channel](https://t.me/awesomeRSSHub) | [X (Twitter)](https://x.com/intent/follow?screen_name=_RSSHub) ## Related Projects -- [RSSHub Radar](https://github.com/DIYgod/RSSHub-Radar) | 一个可以帮助你快速发现和订阅当前网站 RSS 和 RSSHub 的浏览器扩展 -- [RSSBud](https://github.com/Cay-Zhang/RSSBud) ([TestFlight 公测](https://testflight.apple.com/join/rjCVzzHP)) | iOS 平台的 RSSHub Radar,专为移动生态优化 -- [RSSAid](https://github.com/LeetaoGoooo/RSSAid) | 基于 Flutter 构建的 Android 平台的 RSSHub Radar +- [RSSHub Radar](https://github.com/DIYgod/RSSHub-Radar) | A browser extension that can help you quickly discover and subscribe to the RSS and RSSHub of current websites. +- [RSSBud](https://github.com/Cay-Zhang/RSSBud) | RSSHub Radar for iOS platform, designed specifically for mobile ecosystem optimization. +- [RSSAid](https://github.com/LeetaoGoooo/RSSAid) | RSSHub Radar for Android platform built with Flutter. - [DocSearch](https://github.com/Fatpandac/DocSearch) | Link RSSHub DocSearch into Raycast -## Join Us +## Contribute We welcome all pull requests. Suggestions and feedback are also welcomed [here](https://github.com/DIYgod/RSSHub/issues). -Refer to [Join Us](https://docs.rsshub.app/en/joinus/quick-start.html) - -见 [参与我们](https://docs.rsshub.app/joinus/quick-start.html) +Refer to [Quick Start](https://docs.rsshub.app/joinus/) ## Deployment -Refer to [Deployment](https://docs.rsshub.app/en/install/) - -见 [部署](https://docs.rsshub.app/install/) - -## Support RSSHub +Refer to [Deployment](https://docs.rsshub.app/deploy/) -Refer to [Support RSSHub](https://docs.rsshub.app/en/support/) - -见 [支持 RSSHub](https://docs.rsshub.app/support/) - -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. - -You can support RSSHub via donations. - -### Recurring Donation +## Special Thanks -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 [Patreon](https://www.patreon.com/DIYgod) -- Become a Sponser on [爱发电](https://afdian.net/@diygod) -- Contact us directly: i@diygod.me +[![](https://opencollective.com/RSSHub/contributors.svg?width=890)](https://github.com/DIYgod/RSSHub/graphs/contributors) -### One-time Donation +Logo designer [sheldonrrr](https://dribbble.com/sheldonrrr) -We accept donations via the following ways: +[![](https://raw.githubusercontent.com/DIYgod/sponsors/main/sponsors.simple.svg)](https://github.com/DIYgod/sponsors) -- [WeChat Pay](https://diygod.me/images/wx.jpg) -- [Alipay](https://diygod.me/images/zfb.jpg) -- [Paypal](https://www.paypal.me/DIYgod) +               +
## Author **RSSHub** © [DIYgod](https://github.com/DIYgod), Released under the [MIT](./LICENSE) License.
Authored and maintained by DIYgod with help from contributors ([list](https://github.com/DIYgod/RSSHub/contributors)). -> Blog [@DIYgod](https://diygod.me) · GitHub [@DIYgod](https://github.com/DIYgod) · Twitter [@DIYgod](https://twitter.com/DIYgod) · Telegram Channel [@awesomeDIYgod](https://t.me/awesomeDIYgod) +> Blog [@DIYgod](https://diygod.cc) · GitHub [@DIYgod](https://github.com/DIYgod) · X (Twitter) [@DIYgod](https://x.com/DIYgod) · Telegram Channel [@awesomeDIYgod](https://t.me/awesomeDIYgod) diff --git a/SECURITY.md b/SECURITY.md index d7e04b96a72eec..97af6228f31b85 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,4 +6,4 @@ Latest commits in master branch ## Reporting a Vulnerability -If you believe you have found a security vulnerability in RSSHub, please let us know right away, you can email us at [i@diygod.me](mailto:i@diygod.me). We will investigate all legitimate reports and do our best to quickly fix the problem. +If you believe you have found a security vulnerability in RSSHub, please let us know right away, you can [open a draft security advisory](https://github.com/DIYgod/RSSHub/security/advisories/new) or email us at [i@diygod.me](mailto:i@diygod.me). We will investigate all legitimate reports and do our best to quickly fix the problem. diff --git a/api/vercel.js b/api/vercel.js deleted file mode 100644 index 02ad653ab617f4..00000000000000 --- a/api/vercel.js +++ /dev/null @@ -1,14 +0,0 @@ -const path = require('path'); -const moduleAlias = require('module-alias'); -moduleAlias.addAlias('@', path.join(__dirname, '../lib')); - -const config = require('../lib/config'); -config.set({ - NO_LOGFILES: true, -}); - -const app = require('../lib/app'); - -module.exports = (req, res) => { - app.callback()(req, res); -}; diff --git a/api/vercel.ts b/api/vercel.ts new file mode 100644 index 00000000000000..12a2a910b723a3 --- /dev/null +++ b/api/vercel.ts @@ -0,0 +1,17 @@ +const path = require('path'); +const moduleAlias = require('module-alias'); +moduleAlias.addAlias('@', path.join(__dirname, '../lib')); + +const { setConfig } = require('../lib/config'); +setConfig({ + NO_LOGFILES: true, +}); + +const { handle } = require('hono/vercel'); +const app = require('../lib/app'); +const logger = require('../lib/utils/logger'); + +logger.info(`🎉 RSSHub is running! Cheers!`); +logger.info('💖 Can you help keep this open source project alive? Please sponsor 👉 https://docs.rsshub.app/sponsor'); + +module.exports = handle(app); diff --git a/app.json b/app.json index 5232a9445b4249..e351d522029245 100644 --- a/app.json +++ b/app.json @@ -1,6 +1,6 @@ { "name": "RSSHub", - "description": "万物皆可 RSS", + "description": "Everything is RSSible", "repository": "https://github.com/DIYgod/RSSHub", "website": "https://docs.rsshub.app/", "logo": "https://i.loli.net/2019/04/23/5cbeb7e41414c.png", @@ -14,28 +14,8 @@ "value": "80", "required": false }, - "PIXIV_REFRESHTOKEN": { - "required": false - }, - "DISQUS_API_KEY": { - "required": false - }, - "SPOTIFY_CLIENT_ID": { - "required": false - }, - "SPOTIFY_CLIENT_SECRET": { - "required": false - }, - "SPOTIFY_REFRESHTOKEN": { - "required": false - }, - "TWITTER_CONSUMER_KEY": { - "required": false - }, - "TWITTER_CONSUMER_SECRET": { - "required": false - }, - "YOUTUBE_KEY": { + "PUPPETEER_SKIP_DOWNLOAD": { + "value": "1", "required": false } } diff --git a/assets/404.html b/assets/404.html index f8c64279428b4f..2c6b99e4c5a94f 100644 --- a/assets/404.html +++ b/assets/404.html @@ -1,4 +1,4 @@ \ No newline at end of file + diff --git a/assets/CNAME b/assets/CNAME index 5ab3a9991b7eef..7bec7fdc352637 100644 --- a/assets/CNAME +++ b/assets/CNAME @@ -1 +1 @@ -rsshub.js.org \ No newline at end of file +rsshub.js.org diff --git a/assets/index.html b/assets/index.html index f8c64279428b4f..2c6b99e4c5a94f 100644 --- a/assets/index.html +++ b/assets/index.html @@ -1,4 +1,4 @@ \ No newline at end of file + diff --git a/assets/radar-rules.js b/assets/radar-rules.js deleted file mode 100644 index 627147be907b89..00000000000000 --- a/assets/radar-rules.js +++ /dev/null @@ -1,853 +0,0 @@ -({ - // '8world.com': { _name: '8视界', '.': [{ title: '分类', docs: 'https://docs.rsshub.app/new-media.html#_8-shi-jie-fen-lei', source: ['/:category', '/'], target: '/8world/:category?' }] }, - 'aamacau.com': { _name: '論盡媒體 AllAboutMacau Media', '.': [{ title: '话题', docs: 'https://docs.rsshub.app/new-media.html#lun-jin-mei-ti-allaboutmacau-media-hua-ti', source: ['/'], target: '/:category?/:id?' }] }, - 'eprice.com.tw': { _name: 'ePrice', '.': [{ title: 'ePrice 比價王', docs: 'https://docs.rsshub.app/new-media.html#eprice', source: ['/'], target: '/:region?' }] }, - 'eprice.com.hk': { _name: 'ePrice', '.': [{ title: 'ePrice 香港', docs: 'https://docs.rsshub.app/new-media.html#eprice', source: ['/'], target: '/:region?' }] }, - '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' }, - ], - }, - 'https://www.icac.org.hk': { _name: '廉政公署', '.': [{ title: '新闻公布', docs: 'https://docs.rsshub.app/government.html#xiang-gang-lian-zheng-gong-shu', source: ['/:lang/press/index.html'], target: '/icac/news/:lang' }] }, - 'trow.cc': { _name: 'The Ring of Wonder', '.': [{ title: '首页更新', docs: 'https://docs.rsshub.app/bbs.html#the-ring-of-wonder', source: ['/'], target: '/portal' }] }, - 'weibo.com': { - _name: '微博', - '.': [ - { - title: '博主', - docs: 'https://docs.rsshub.app/social-media.html#wei-bo', - source: ['/u/:id', '/:id'], - target: (params, url, document) => { - let uid = document?.documentElement.innerHTML.match(/\$CONFIG\['oid']='(\d+)'/)?.[1]; - if (!uid && !isNaN(params.id)) { - uid = params.id; - } - return uid ? `/weibo/user/${uid}` : ''; - }, - }, - { title: '关键词', docs: 'https://docs.rsshub.app/social-media.html#wei-bo' }, - { title: '超话', docs: 'https://docs.rsshub.app/social-media.html#wei-bo', source: '/p/:id/super_index', target: '/weibo/super_index/:id' }, - ], - s: [{ title: '热搜榜', docs: 'https://docs.rsshub.app/social-media.html#wei-bo', source: '/top/summary', target: '/weibo/search/hot' }], - }, - 'weibo.cn': { _name: '微博', m: [{ title: '博主', docs: 'https://docs.rsshub.app/social-media.html#wei-bo', source: ['/u/:uid', '/profile/:uid'], target: '/weibo/user/:uid' }] }, - 'github.com': { - _name: 'GitHub', - '.': [ - { title: '用户仓库', docs: 'https://docs.rsshub.app/programming.html#github', source: '/:user', target: '/github/repos/:user' }, - { title: '用户 Followers', docs: 'https://docs.rsshub.app/programming.html#github', source: '/:user', target: '/github/user/followers/:user' }, - { title: 'Trending', docs: 'https://docs.rsshub.app/programming.html#github', source: '/trending', target: '/github/trending/:since' }, - { title: 'Trending', docs: 'https://docs.rsshub.app/programming.html#github', source: '/topics', target: '/github/topics/:name/:qs?' }, - { title: '仓库 Issue', docs: 'https://docs.rsshub.app/programming.html#github', source: ['/:user/:repo/issues', '/:user/:repo/issues/:id', '/:user/:repo'], target: '/github/issue/:user/:repo' }, - { title: '仓库 Pull Requests', docs: 'https://docs.rsshub.app/programming.html#github', source: ['/:user/:repo/pulls', '/:user/:repo/pulls/:id', '/:user/:repo'], target: '/github/pull/:user/:repo' }, - { title: '仓库 Stars', docs: 'https://docs.rsshub.app/programming.html#github', source: ['/:user/:repo/stargazers', '/:user/:repo'], target: '/github/stars/:user/:repo' }, - { title: '仓库 Branches', docs: 'https://docs.rsshub.app/programming.html#github', source: ['/:user/:repo/branches', '/:user/:repo'], target: '/github/branches/:user/:repo' }, - { title: '文件 Commits', docs: 'https://docs.rsshub.app/programming.html#github', source: '/:user/:repo/blob/:branch/*filepath', target: '/github/file/:user/:repo/:branch/:filepath' }, - { title: '用户 Starred Repositories', docs: 'https://docs.rsshub.app/programming.html#github', source: '/:user', target: '/github/starred_repos/:user' }, - { title: '仓库 Contributors', docs: 'https://docs.rsshub.app/programming.html#github', source: ['/:user/:repo/graphs/contributors', '/:user/:repo'], target: '/github/contributors/:user/:repo' }, - ], - }, - 'smzdm.com': { - _name: '什么值得买', - www: [{ title: '排行榜', docs: 'https://docs.rsshub.app/shopping.html#shen-me-zhi-de-mai', source: '/top' }], - search: [{ title: '关键词', docs: 'https://docs.rsshub.app/shopping.html#shen-me-zhi-de-mai', source: '/', target: (params, url) => `/smzdm/keyword/${new URL(url).searchParams.get('s')}` }], - }, - 'ximalaya.com': { - _name: '喜马拉雅', - '.': [ - { - title: '专辑', - docs: 'https://docs.rsshub.app/multimedia.html#xi-ma-la-ya', - source: '/:type/:id', - target: (params) => { - if (parseInt(params.id) + '' === params.id) { - return '/ximalaya/:type/:id/'; - } - }, - }, - ], - }, - 'algocasts.io': { _name: 'AlgoCasts', '.': [{ title: '视频更新', docs: 'https://docs.rsshub.app/programming.html#algocasts', source: '/episodes', target: '/algocasts' }] }, - 'soulapp.cn': { _name: 'Soul', '.': [{ title: '瞬间更新', docs: 'https://docs.rsshub.app/social-media.html#soul' }] }, - 'anime1.me': { - _name: 'Anime1', - '.': [ - { title: '動畫', docs: 'https://docs.rsshub.app/anime.html#anime1', source: '/category/:time/:name', target: '/anime1/anime/:time/:name' }, - { - title: '搜尋', - docs: 'https://docs.rsshub.app/anime.html#anime1', - source: '/', - target: (params, url) => { - const keyword = new URL(url).searchParams.get('s'); - return keyword ? `/anime1/search/${keyword}` : ''; - }, - }, - ], - }, - 'swufe.edu.cn': { - _name: '西南财经大学', - it: [ - { title: '经济信息工程学院 - 通知公告', docs: 'https://docs.rsshub.app/university.html#xi-nan-cai-jing-da-xue', source: '/index/tzgg.htm', target: '/swufe/seie/tzgg' }, - { title: '经济信息工程学院 - 学院新闻', docs: 'https://docs.rsshub.app/university.html#xi-nan-cai-jing-da-xue', source: '/index/xyxw.htm', target: '/swufe/seie/xyxw' }, - ], - }, - 'ishuhui.com': { _name: '鼠绘漫画', www: [{ title: '鼠绘漫画', docs: 'https://docs.rsshub.app/anime.html#shu-hui-man-hua', source: '/comics/anime/:id', target: '/shuhui/comics/:id' }] }, - 'www.chicagotribune.com': { _name: 'Chicago Tribune', www: [{ title: 'Chicago Tribune', docs: 'https://docs.rsshub.app/traditional_media.html#chicago-tribune', source: '/' }] }, - 'haimaoba.com': { _name: '海猫吧', www: [{ title: '漫画更新', docs: 'https://docs.rsshub.app/anime.html#hai-mao-ba', source: '/catalog/:id', target: '/haimaoba/:id' }] }, - 'copymanga.com': { _name: '拷贝漫画', www: [{ title: '漫画更新', docs: 'https://docs.rsshub.app/anime.html#kao-bei-man-hua', source: '/comic/:id/', target: '/manhuagui/comic/:id/5' }] }, - 'pgyer.com': { _name: '蒲公英应用分发', www: [{ title: 'app更新', docs: 'https://docs.rsshub.app/program-update.html#pu-gong-ying-ying-yong-fen-fa', source: '/:app', target: '/pgyer/:app' }] }, - 'wineyun.com': { _name: '酒云网', www: [{ title: '最新商品', docs: 'https://docs.rsshub.app/other.html#jiu-yun-wang', source: ['/:category'], target: '/wineyun/:category' }] }, - 'epicgames.com': { _name: 'Epic Games', www: [{ title: '每周免费游戏', docs: 'https://docs.rsshub.app/game.html#epicgames-freegame', source: '/store/zh-CN/free-games', target: '/epicgames/freegames' }] }, - 'playstation.com': { - _name: 'PlayStation', - store: [ - { title: '游戏列表', docs: 'https://docs.rsshub.app/game.html#playstation', source: '/zh-hans-hk/grid/:id/:page', target: '/ps/list/:id' }, - { title: '折扣|价格', docs: 'https://docs.rsshub.app/game.html#playstation', source: ['/:lang/product/:gridName'], target: '/ps/:lang/product/:gridName' }, - ], - www: [ - { title: '用户奖杯', docs: 'https://docs.rsshub.app/game.html#playstation' }, - { title: '系统更新纪录', docs: 'https://docs.rsshub.app/game.html#playstation' }, - ], - }, - 'monsterhunter.com': { - _name: '怪物猎人世界', - www: [ - { title: '更新情报', docs: 'https://docs.rsshub.app/game.html#guai-wu-lie-ren-shi-jie', source: ['', '/*tpath'], target: '/mhw/update' }, - { title: '最新消息', docs: 'https://docs.rsshub.app/game.html#guai-wu-lie-ren-shi-jie', source: ['', '/*tpath'], target: '/mhw/news' }, - ], - }, - 'vgtime.com': { - _name: '游戏时光', - www: [ - { title: '新闻', docs: 'https://docs.rsshub.app/game.html#you-xi-shi-guang', source: '/topic/index.jhtml', target: '/vgtime/news' }, - { title: '游戏发售表', docs: 'https://docs.rsshub.app/game.html#you-xi-shi-guang', source: '/game/release.jhtml', target: '/vgtime/release' }, - { title: '关键词资讯', docs: 'https://docs.rsshub.app/game.html#you-xi-shi-guang', source: '/search/list.jhtml', target: (params, url) => `/vgtime/keyword/${new URL(url).searchParams.get('keyword')}` }, - ], - }, - 'bing.com': { _name: 'Bing', www: [{ title: '每日壁纸', docs: 'https://docs.rsshub.app/picture.html#bing-bi-zhi', source: '', target: '/bing' }] }, - 'wegene.com': { - _name: 'WeGene', - www: [ - { title: '最近更新', docs: 'https://docs.rsshub.app/other.html#wegene', source: '', target: '/wegene/newest' }, - { title: '栏目', docs: 'https://docs.rsshub.app/other.html#wegene', source: '/crowdsourcing', target: '/wegene/column/all/all' }, - ], - }, - 'qdaily.com': { - _name: '好奇心日报', - www: [ - { title: '标签', docs: 'https://docs.rsshub.app/new-media.html#hao-qi-xin-ri-bao', source: '/tags/:idd', target: (params) => `/qdaily/tag/${params.idd.replace('.html', '')}` }, - { title: '栏目', docs: 'https://docs.rsshub.app/new-media.html#hao-qi-xin-ri-bao', source: '/special_columns/:idd', target: (params) => `/qdaily/column/${params.idd.replace('.html', '')}` }, - { title: '分类', docs: 'https://docs.rsshub.app/new-media.html#hao-qi-xin-ri-bao', source: '/categories/:idd', target: (params) => `/qdaily/category/${params.idd.replace('.html', '')}` }, - ], - }, - '3ycy.com': { _name: '三界异次元', www: [{ title: '最近更新', docs: 'https://docs.rsshub.app/anime.html#san-jie-yi-ci-yuan', source: '/', target: '/3ycy/home' }] }, - 'emi-nitta.net': { - _name: 'Emi Nitta', - '.': [ - { title: '最近更新', docs: 'https://docs.rsshub.app/other.html#xin-tian-hui-hai-guan-fang-wang-zhan', source: '/updates', target: '/emi-nitta/updates' }, - { title: '新闻', docs: 'https://docs.rsshub.app/other.html#xin-tian-hui-hai-guan-fang-wang-zhan', source: '/contents/news', target: '/emi-nitta/news' }, - ], - }, - 'alter-shanghai.cn': { _name: 'Alter', '.': [{ title: '新闻', docs: 'https://docs.rsshub.app/shopping.html#alter-zhong-guo', source: '/cn/news.html', target: '/alter-cn/news' }] }, - 'itslide.com': { _name: 'ITSlide', www: [{ title: '最新', docs: 'https://docs.rsshub.app/programming.html#itslide', source: '/*', target: '/itslide/new' }] }, - 'leboncoin.fr': { _name: 'leboncoin', www: [{ title: 'ads', docs: 'https://docs.rsshub.app/en/shopping.html#leboncoin', source: '/recherche', target: (params, url) => '/leboncoin/ad/' + url.split('?')[1] }] }, - 'yuancheng.work': { - _name: '远程.work', - '.': [ - { - title: '招聘信息', - docs: 'https://docs.rsshub.app/other.html#yuan-cheng-work', - source: '/:caty', - target: (params, url) => { - if (!url) { - return '/remote-work'; - } - return '/remote-work/' + /\w+-(\w+)-\w+/.exec(url)[1]; - }, - }, - ], - }, - 'chinatimes.com': { _name: '中時電子報', www: [{ title: '新聞', docs: 'https://docs.rsshub.app/traditional-media.html#zhong-shi-dian-zi-bao', source: '/:caty', target: (params) => '/chinatimes/' + params.caty }] }, - 'ithome.com': { - _name: 'IT 之家', - '.': [ - { title: '24 小时阅读榜', docs: 'https://docs.rsshub.app/new-media.html#it-zhi-jia', source: ['', '/*'], target: '/ithome/ranking/24h' }, - { title: '7 天最热', docs: 'https://docs.rsshub.app/new-media.html#it-zhi-jia', source: ['', '/*'], target: '/ithome/ranking/7days' }, - { title: '月榜', docs: 'https://docs.rsshub.app/new-media.html#it-zhi-jia', source: ['', '/*'], target: '/ithome/ranking/monthly' }, - ], - it: [{ title: 'IT 资讯', docs: 'https://docs.rsshub.app/new-media.html#it-zhi-jia', source: '/', target: '/ithome/it' }], - soft: [{ title: '软件之家', docs: 'https://docs.rsshub.app/new-media.html#it-zhi-jia', source: '/', target: '/ithome/soft' }], - win10: [{ title: 'win10 之家', docs: 'https://docs.rsshub.app/new-media.html#it-zhi-jia', source: '/', target: '/ithome/win10' }], - iphone: [{ title: 'iphone 之家', docs: 'https://docs.rsshub.app/new-media.html#it-zhi-jia', source: '/', target: '/ithome/iphone' }], - ipad: [{ title: 'ipad 之家', docs: 'https://docs.rsshub.app/new-media.html#it-zhi-jia', source: '/', target: '/ithome/ipad' }], - android: [{ title: 'android 之家', docs: 'https://docs.rsshub.app/new-media.html#it-zhi-jia', source: '/', target: '/ithome/android' }], - digi: [{ title: '数码之家', docs: 'https://docs.rsshub.app/new-media.html#it-zhi-jia', source: '/', target: '/ithome/digi' }], - next: [{ title: '智能时代', docs: 'https://docs.rsshub.app/new-media.html#it-zhi-jia', source: '/', target: '/ithome/next' }], - }, - 'govopendata.com': { _name: '新闻联播文字版', cn: [{ title: '新闻联播文字版', docs: 'https://docs.rsshub.app/traditional-media.html#xin-wen-lian-bo-wen-zi-ban', source: '/xinwenlianbo', target: '/xinwenlianbo/index' }] }, - 'steampowered.com': { _name: 'Steam', store: [{ title: 'search', docs: 'https://docs.rsshub.app/game.html#steam', source: '/search/', target: (params, url) => `/steam/search/${new URL(url).searchParams}` }] }, - // 'baijingapp.com': { _name: '白鲸出海', www: [{ title: '文章', docs: 'https://docs.rsshub.app/new-media.html#bai-jing-chu-hai', source: '', target: '/baijing' }] }, - 'xiaomi.cn': { _name: '小米社区', www: [{ title: '圈子', docs: 'https://docs.rsshub.app/bbs.html#xiao-mi-she-qu', source: '/board/:boardId', target: '/mi/bbs/board/:boardId' }] }, - '163.com': { - _name: '网易', - ds: [{ title: '大神', docs: 'https://docs.rsshub.app/game.html#wang-yi-da-shen', source: '/user/:id', target: '/netease/ds/:id' }], - open: [ - { title: '公开课 - 精品课程', docs: 'https://docs.rsshub.app/study.html#wang-yi-gong-kai-ke', source: '/', target: '/open163/vip' }, - { title: '公开课 - 最新课程', docs: 'https://docs.rsshub.app/study.html#wang-yi-gong-kai-ke', source: '/', target: '/open163/latest' }, - ], - music: [ - { - title: '云音乐 - 用户歌单', - docs: 'https://docs.rsshub.app/multimedia.html#wang-yi-yun-yin-yue', - source: '/', - target: (params, url) => { - const id = new URL(url).hash.match(/home\?id=(.*)/)[1]; - return id ? `/ncm/user/playlist/${id}` : ''; - }, - }, - { - title: '云音乐 - 歌单歌曲', - docs: 'https://docs.rsshub.app/multimedia.html#wang-yi-yun-yin-yue', - source: '/', - target: (params, url) => { - const id = new URL(url).hash.match(/playlist\?id=(.*)/)[1]; - return id ? `/ncm/playlist/${id}` : ''; - }, - }, - { - title: '云音乐 - 歌手专辑', - docs: 'https://docs.rsshub.app/multimedia.html#wang-yi-yun-yin-yue', - source: '/', - target: (params, url) => { - const id = new URL(url).hash.match(/album\?id=(.*)/)[1]; - return id ? `/ncm/artist/${id}` : ''; - }, - }, - { - title: '云音乐 - 电台节目', - docs: 'https://docs.rsshub.app/multimedia.html#wang-yi-yun-yin-yue', - source: '/', - target: (params, url) => { - const id = new URL(url).hash.match(/djradio\?id=(.*)/)[1]; - return id ? `/ncm/djradio/${id}` : ''; - }, - }, - ], - 'y.music': [ - { title: '云音乐 - 用户歌单', docs: 'https://docs.rsshub.app/multimedia.html#wang-yi-yun-yin-yue', source: '/m/user', target: (params, url) => `/ncm/playlist/${new URL(url).searchParams.get('id')}` }, - { title: '云音乐 - 歌单歌曲', docs: 'https://docs.rsshub.app/multimedia.html#wang-yi-yun-yin-yue', source: '/m/playlist', target: (params, url) => `/ncm/playlist/${new URL(url).searchParams.get('id')}` }, - { title: '云音乐 - 歌手专辑', docs: 'https://docs.rsshub.app/multimedia.html#wang-yi-yun-yin-yue', source: '/m/album', target: (params, url) => `/ncm/playlist/${new URL(url).searchParams.get('id')}` }, - { title: '云音乐 - 播单声音', docs: 'https://docs.rsshub.app/multimedia.html#wang-yi-yun-yin-yue', source: ['/m/radio', '/m/djradio'], target: (params, url) => `/ncm/playlist/${new URL(url).searchParams.get('id')}` }, - ], - }, - 'suzhou.gov.cn': { _name: '苏州市政府', www: [{ title: '政府新闻', docs: 'https://docs.rsshub.app/government.html#su-zhou-shi-ren-min-zheng-fu', source: '/szsrmzf/:uid/nav_list.shtml', target: '/gov/suzhou/news/:uid' }] }, - 'mqube.net': { - _name: 'MQube', - www: [ - { title: '全站最近更新', docs: 'https://docs.rsshub.app/multimedia.html#mqube', source: '/', target: '/mqube/latest' }, - { title: '全站每日排行', docs: 'https://docs.rsshub.app/multimedia.html#mqube', source: '/', target: '/mqube/top' }, - { title: '个人最近更新', docs: 'https://docs.rsshub.app/multimedia.html#mqube', source: '/user/:user', target: '/mqube/user/:user' }, - { title: '标签最近更新', docs: 'https://docs.rsshub.app/multimedia.html#mqube', source: '/search/tag/:tag', target: '/mqube/tag/:tag' }, - ], - }, - 'nikkei.com': { _name: '日本経済新聞', www: [{ title: 'ホームページ', docs: 'https://docs.rsshub.app/traditional-media.html#ri-ben-jing-ji-xin-wen', source: '/', target: '/nikkei/index' }] }, - 'last.fm': { - _name: 'Last.fm', - www: [ - { title: '用户播放记录', docs: 'https://docs.rsshub.app/multimedia.html#last-fm', source: ['/user/:user', '/user/:user/*'], target: '/lastfm/recent/:user' }, - { title: '用户 Love 记录', docs: 'https://docs.rsshub.app/multimedia.html#last-fm', source: ['/user/:user', '/user/:user/*'], target: '/lastfm/loved/:user' }, - { title: '站内 Top 榜单', docs: 'https://docs.rsshub.app/multimedia.html#last-fm', source: '/charts', target: '/lastfm/top' }, - ], - }, - 'ddrk.me': { - _name: '低端影视', - www: [ - { title: '首页', docs: 'https://docs.rsshub.app/multimedia.html#di-duan-ying-shi', source: '/', target: '/ddrk/index' }, - { title: '标签', docs: 'https://docs.rsshub.app/multimedia.html#di-duan-ying-shi', source: '/tag/:tag', target: '/ddrk/tag/:tag' }, - { title: '分类', docs: 'https://docs.rsshub.app/multimedia.html#di-duan-ying-shi', source: ['/category/:category', '/category/:uplevel/:category'], target: '/ddrk/category/:category' }, - { - title: '影视剧集更新', - docs: 'https://docs.rsshub.app/multimedia.html#di-duan-ying-shi', - source: ['/:name', '/:name/:season'], - target: (params) => { - if (params.name !== 'category' && params.name !== 'tag' && params.name !== 'ddrklogin' && params.name !== 'about' && params.name !== 'deleted') { - return `/ddrk/update/${params.name}${params.season ? '/' + params.season : ''}`; - } - }, - }, - ], - }, - 'google.com': { - _name: '谷歌', - photos: [ - { - title: '相册', - docs: 'https://docs.rsshub.app/picture.html#google-xiang-ce', - source: '/share/*', - target: (params, url, document) => { - const id = document && document.querySelector('html').innerHTML.match(/photos.app.goo.gl\/(.*?)"/)[1]; - return id ? `/google/album/${id}` : ''; - }, - }, - ], - sites: [{ title: 'Sites', docs: 'https://docs.rsshub.app/blog.html#google-sites', source: ['/site/:id/*', '/site/:id'], target: '/google/sites/:id' }], - }, - 'hackerone.com': { _name: 'HackerOne', '.': [{ title: 'HackerOne Hacker Activity', docs: 'https://docs.rsshub.app/other.html#hackerone-hacker-activity', source: '/hacktivity', target: '/hackerone/hacktivity' }] }, - 'cowlevel.net': { _name: '奶牛关', '.': [{ title: '元素文章', docs: 'https://docs.rsshub.app/game.html#nai-niu-guan', source: ['/element/:id', '/element/:id/article'], target: '/cowlevel/element/:id' }] }, - 'beijing.gov.cn': { wjw: [{ title: '北京卫生健康委员会', docs: 'https://docs.rsshub.app/government.html#bei-jing-shi-wei-sheng-jian-kang-wei-yuan-hui', source: '/xwzx_20031/:caty', target: '/gov/beijing/mhc/:caty' }] }, - 'ynu.edu.cn': { - _name: '云南大学', - home: [{ title: '官网消息通告', docs: 'https://docs.rsshub.app/university.html#yun-nan-da-xue', source: '/tzgg.htm', target: '/ynu/home' }], - jwc: [ - { title: '教务处教务科通知', docs: 'https://docs.rsshub.app/university.html#yun-nan-da-xue', source: '/*', target: '/jwc/1' }, - { title: '教务处学籍科通知', docs: 'https://docs.rsshub.app/university.html#yun-nan-da-xue', source: '/*', target: '/jwc/2' }, - { title: '教务处教学研究科通知', docs: 'https://docs.rsshub.app/university.html#yun-nan-da-xue', source: '/*', target: '/jwc/3' }, - { title: '教务处实践科学科通知', docs: 'https://docs.rsshub.app/university.html#yun-nan-da-xue', source: '/*', target: '/jwc/4' }, - ], - grs: [{ title: '研究生院通知', docs: 'https://docs.rsshub.app/university.html#yun-nan-da-xue', source: '/*', target: '' }], - }, - 'kuaidi100.com': { - _name: '快递100', - '.': [ - { - title: '快递追踪', - docs: 'https://docs.rsshub.app/other.html#kuai-di-100', - source: '/', - target: (params, url, document) => { - const postid = document && document.querySelector('#postid').value; - const com = document && document.querySelector('#selectComBtn').childNodes[1].attributes[1].value; - if (com && com !== 'default' && postid) { - return `/kuaidi100/track/${com}/${postid}`; - } - }, - }, - { title: '支持的快递公司列表', docs: 'https://docs.rsshub.app/other.html#kuai-di-100', source: '/', target: '/kuaidi100/company' }, - ], - }, - 'japanpost.jp': { - _name: '日本郵便', - 'trackings.post': [ - { - title: '郵便・荷物の追跡', - docs: 'https://docs.rsshub.app/other.html#ri-ben-you-bian-you-bian-zhui-ji-サービス', - source: '/services/srv/search/direct', - target: (params, url) => { - const reqCode = new URL(url).searchParams.get('reqCodeNo1').toUpperCase(); - const locale = new URL(url).searchParams.get('locale').toLowerCase(); - if ((reqCode.search(/^(?:\d{11,12}|[A-Z]{2}\d{9}[A-Z]{2})$/) === 0 && locale === 'ja') || locale === 'en') { - return `/japanpost/track/${reqCode}/${locale}`; - } - }, - }, - ], - }, - // 'biquge5200.com': { www: [{ title: 'biquge5200.com', docs: 'https://docs.rsshub.app/reading.html#bi-qu-ge-biquge5200-com', source: '/:id', target: '/novel/biquge/:id' }] }, - // 'biquge.info': { www: [{ title: 'biquge.info', docs: 'https://docs.rsshub.app/reading.html#bi-qu-ge-biquge-info', source: '/:id', target: '/novel/biqugeinfo/:id' }] }, - 'matters.news': { - _name: 'Matters', - '.': [ - { title: '最新排序', docs: 'https://docs.rsshub.app/new-media.html#matters', source: '', target: '/matters/latest' }, - { title: '标签', docs: 'https://docs.rsshub.app/new-media.html#matters', source: '/tags/:tid', target: '/matters/tags/:tid' }, - { - title: '作者', - docs: 'https://docs.rsshub.app/new-media.html#matters', - source: ['/:id', '/:id/comments'], - target: (params) => { - const uid = params.id.replace('@', ''); - return uid ? `/matters/author/${uid}` : ''; - }, - }, - ], - }, - 'zhaishuyuan.com': { _name: '斋书苑', '.': [{ title: '最新章节', docs: 'https://docs.rsshub.app/reading.html#zhai-shu-yuan', source: ['/book/:id', '/read/:id'], target: '/novel/zhaishuyuan/:id' }] }, - 'hbut.edu.cn': { - _name: '湖北工业大学', - www: [ - { - title: '新闻中心', - docs: 'http://docs.rsshub.app/university.html#hu-bei-gong-ye-da-xue', - source: '/xwzx/:name', - target: (params) => { - const type = params.name.replace('.htm', ''); - return type ? `/hbut/news/${type}` : '/hbut/news/tzgg'; - }, - }, - ], - jsjxy: [ - { title: '新闻动态', docs: 'http://docs.rsshub.app/university.html#hu-bei-gong-ye-da-xue', source: '/index/xwdt.htm', target: '/hbut/cs/xwdt' }, - { title: '通知公告', docs: 'http://docs.rsshub.app/university.html#hu-bei-gong-ye-da-xue', source: '/index/tzgg.htm', target: '/hbut/cs/tzgg' }, - { title: '教学信息', docs: 'http://docs.rsshub.app/university.html#hu-bei-gong-ye-da-xue', source: '/jxxx.htm', target: '/hbut/cs/jxxx' }, - { title: '科研动态', docs: 'http://docs.rsshub.app/university.html#hu-bei-gong-ye-da-xue', source: '/kxyj/kydt.htm', target: '/hbut/cs/kydt' }, - { title: '党建活动', docs: 'http://docs.rsshub.app/university.html#hu-bei-gong-ye-da-xue', source: '/djhd/djhd.htm', target: '/hbut/cs/djhd' }, - ], - }, - // 'zcool.com.cn': { - // _name: '站酷', - // www: [ - // { title: '发现', docs: 'https://docs.rsshub.app/design.html#zhan-ku', source: '', target: '/zcool/discover' }, - // { title: '发现 - 精选 - 全部推荐', docs: 'https://docs.rsshub.app/design.html#zhan-ku', source: '', target: '/zcool/discover/all' }, - // { title: '发现 - 精选 - 首页推荐', docs: 'https://docs.rsshub.app/design.html#zhan-ku', source: '', target: '/zcool/discover/home' }, - // { title: '发现 - 精选 - 编辑精选', docs: 'https://docs.rsshub.app/design.html#zhan-ku', source: '', target: '/zcool/discover/home' }, - // { title: '发现 - 精选 - 文章 - 编辑精选', docs: 'https://docs.rsshub.app/design.html#zhan-ku', source: '', target: '/zcool/discover/article' }, - // { title: '作品榜单', docs: 'https://docs.rsshub.app/design.html#zhan-ku', source: '', target: '/zcool/top/design' }, - // { title: '文章榜单', docs: 'https://docs.rsshub.app/design.html#zhan-ku', source: '', target: '/zcool/top/article' }, - // { title: '用户作品', docs: 'https://docs.rsshub.app/design.html#zhan-ku', source: ['/u/:id'], target: '/zcool/user/:id' }, - // ], - // }, - 't.me': { - _name: 'Telegram', - '.': [ - { - title: '频道', - docs: 'https://docs.rsshub.app/social-media.html#telegram', - source: '/:username', - target: (params, url, document) => { - const isChannel = document && document.querySelector('.tgme_action_button_label'); - if (isChannel) { - return '/telegram/channel/:username'; - } - }, - }, - { title: '频道', docs: 'https://docs.rsshub.app/social-media.html#telegram', source: '/s/:username', target: '/telegram/channel/:username' }, - ], - }, - 'zhuixinfan.com': { _name: '追新番日剧站', '.': [{ title: '更新列表', docs: 'https://docs.rsshub.app/multimedia.html#zhui-xin-fan-ri-ju-zhan', source: ['/main.php'], target: '/zhuixinfan/list' }] }, - 'etoland.co.kr': { - _name: 'eTOLAND', - '.': [{ title: '主题贴', docs: 'https://docs.rsshub.app/bbs.html#etoland', source: ['/bbs/board.php', '/plugin/mobile/board.php'], target: (params, url) => `/etoland/${new URL(url).searchParams.get('bo_table')}` }], - }, - 'onejav.com': { - _name: 'OneJAV BT', - '.': [ - { - title: '今日种子', - docs: 'https://docs.rsshub.app/multimedia.html#onejav', - source: '/', - target: (params, url, document) => { - const today = document.querySelector('div.card.mb-1.card-overview').getAttribute('data-date').replace(/-/g, ''); - return `/onejav/day/${today}`; - }, - }, - { - title: '今日演员', - docs: 'https://docs.rsshub.app/multimedia.html#onejav', - source: '/', - target: (params, url, document) => { - const star = document.querySelector('div.card-content > div > a').getAttribute('href'); - return `/onejav${star}`; - }, - }, - { - title: '页面种子', - docs: 'https://docs.rsshub.app/multimedia.html#onejav', - source: ['/:type', '/:type/:key', '/:type/:key/:morekey'], - target: (params, url, document) => { - const itype = params.morekey === undefined ? params.type : params.type === 'tag' ? 'tag' : 'day'; - let ikey = `${itype === 'day' ? params.type : ''}${params.key || ''}${itype === 'tag' && params.morekey !== undefined ? '%2F' : ''}${params.morekey || ''}`; - if (ikey === '' && itype === 'tag') { - ikey = document.querySelector('div.thumbnail.is-inline > a').getAttribute('href').replace('/tag/', '').replace('/', '%2F'); - } else if (ikey === '' && itype === 'actress') { - ikey = document.querySelector('div.card > a').getAttribute('href').replace('/actress/', ''); - } - return `/onejav/${itype}/${ikey}`; - }, - }, - ], - }, - '141jav.com': { - _name: '141JAV BT', - '.': [ - { - title: '今日种子', - docs: 'https://docs.rsshub.app/multimedia.html#141jav', - source: '/', - target: (params, url, document) => { - const today = document.querySelector('div.card.mb-1.card-overview').getAttribute('data-date').replace(/-/g, ''); - return `/141jav/day/${today}`; - }, - }, - { - title: '今日演员', - docs: 'https://docs.rsshub.app/multimedia.html#141jav', - source: '/', - target: (params, url, document) => { - const star = document.querySelector('div.card-content > div > a').getAttribute('href'); - return `/141jav${star}`; - }, - }, - { - title: '页面种子', - docs: 'https://docs.rsshub.app/multimedia.html#141jav', - source: ['/:type', '/:type/:key', '/:type/:key/:morekey'], - target: (params, url, document) => { - const itype = params.morekey === undefined ? params.type : params.type === 'tag' ? 'tag' : 'day'; - let ikey = `${itype === 'day' ? params.type : ''}${params.key || ''}${itype === 'tag' && params.morekey !== undefined ? '%2F' : ''}${params.morekey || ''}`; - if (ikey === '' && itype === 'tag') { - ikey = document.querySelector('div.thumbnail.is-inline > a').getAttribute('href').replace('/tag/', '').replace('/', '%2F'); - } else if (ikey === '' && itype === 'actress') { - ikey = document.querySelector('div.card > a').getAttribute('href').replace('/actress/', ''); - } - return `/141jav/${itype}/${ikey}`; - }, - }, - ], - }, - '141ppv.com': { - _name: '141ppv BT', - '.': [ - { - title: '今日种子', - docs: 'https://docs.rsshub.app/multimedia.html#141pvp', - source: '/', - target: (params, url, document) => { - const today = document.querySelector('div.card.mb-1.card-overview').getAttribute('data-date').replace(/-/g, ''); - return `/141ppv/day/${today}`; - }, - }, - { - title: '今日演员', - docs: 'https://docs.rsshub.app/multimedia.html#141ppv', - source: '/', - target: (params, url, document) => { - const star = document.querySelector('div.card-content > div > a').getAttribute('href'); - return `/141ppv${star}`; - }, - }, - { - title: '页面种子', - docs: 'https://docs.rsshub.app/multimedia.html#141ppv', - source: ['/:type', '/:type/:key', '/:type/:key/:morekey'], - target: (params, url, document) => { - const itype = params.morekey === undefined ? params.type : params.type === 'tag' ? 'tag' : 'day'; - let ikey = `${itype === 'day' ? params.type : ''}${params.key || ''}${itype === 'tag' && params.morekey !== undefined ? '%2F' : ''}${params.morekey || ''}`; - if (ikey === '' && itype === 'tag') { - ikey = document.querySelector('div.thumbnail.is-inline > a').getAttribute('href').replace('/tag/', '').replace('/', '%2F'); - } else if (ikey === '' && itype === 'actress') { - ikey = document.querySelector('div.card > a').getAttribute('href').replace('/actress/', ''); - } - return `/141ppv/${itype}/${ikey}`; - }, - }, - ], - }, - 'sexinsex.net': { - _name: 'sexinsex', - '.': [ - { - title: '分区帖子', - docs: 'https://docs.rsshub.app/multimedia.html#sexinsex', - source: '/bbs/:path', - target: (params, url) => { - let pid, typeid; - const static_matched = params.path.match(/forum-(\d+)-\d+.html/); - if (static_matched) { - pid = static_matched[1]; - } else if (params.path === 'forumdisplay.php') { - pid = new URL(url).searchParams.get('fid'); - typeid = new URL(url).searchParams.get('typeid'); - } else { - return false; - } - return `/sexinsex/${pid}/${typeid ? typeid : ''}`; - }, - }, - ], - }, - 't66y.com': { - _name: '草榴社区', - www: [ - { - title: '分区帖子', - docs: 'https://docs.rsshub.app/multimedia.html#cao-liu-she-qu', - source: '/thread0806.php', - target: (params, url) => { - const id = new URL(url).searchParams.get('fid'); - const type = new URL(url).searchParams.get('type'); - return `/t66y/${id}/${type ? type : ''}`; - }, - }, - ], - }, - 'umass.edu': { - _name: 'UMASS Amherst', - ece: [ - { title: 'ECE News', docs: 'http://docs.rsshub.app/en/university.html#umass-amherst', source: '/news', target: '/umass/amherst/ecenews' }, - { title: 'ECE Seminar', docs: 'http://docs.rsshub.app/en/university.html#umass-amherst', source: '/seminars', target: '/umass/amherst/eceseminar' }, - ], - 'www.cics': [{ title: 'CICS News', docs: 'http://docs.rsshub.app/en/university.html#umass-amherst', source: '/news', target: '/umass/amherst/csnews' }], - www: [ - { title: 'IPO Events', docs: 'http://docs.rsshub.app/en/university.html#umass-amherst', source: '/ipo/iss/events', target: '/umass/amherst/ipoevents' }, - { title: 'IPO Featured Stories', docs: 'http://docs.rsshub.app/en/university.html#umass-amherst', source: '/ipo/iss/featured-stories', target: '/umass/amherst/ipostories' }, - ], - }, - 'yuque.com': { - _name: '语雀', - www: [ - { - title: '知识库', - docs: 'https://docs.rsshub.app/study.html#yu-que', - source: ['/:space/:book'], - target: (params, url, document) => { - const match = document.documentElement.innerHTML.match(/JSON\.parse\(decodeURIComponent\("(.*)"\)/); - if (match && match[1]) { - const dataStr = match[1]; - try { - const appData = JSON.parse(decodeURIComponent(dataStr)); - return `/yuque/doc/${appData.book.id}`; - } catch (e) { - // pass - } - } - }, - }, - ], - }, - 'bjeea.com': { - _name: '北京考试院', - www: [ - { title: '首页 / 通知公告', docs: 'https://docs.rsshub.app/government.html#bei-jing-jiao-yu-kao-shi-yuan', source: ['/html/bjeeagg'], target: '/gov/beijing/bjeea/bjeeagg' }, - { title: '首页 / 招考政策', docs: 'https://docs.rsshub.app/government.html#bei-jing-jiao-yu-kao-shi-yuan', source: ['/html/zkzc'], target: '/gov/beijing/bjeea/zkzc' }, - { title: '首页 / 自考快递', docs: 'https://docs.rsshub.app/government.html#bei-jing-jiao-yu-kao-shi-yuan', source: ['/html/zkkd'], target: '/gov/beijing/bjeea/zkkd' }, - ], - }, - // 'hk01.com': { - // _name: '香港01', - // www: [ - // { title: '最 Hit', docs: 'https://docs.rsshub.app/traditional-media.html#xiang-gang-01', source: ['/hot', '/'], target: '/hk01/hot' }, - // { title: 'zone', docs: 'https://docs.rsshub.app/traditional-media.html#xiang-gang-01', source: '/zone/:id/:title', target: '/hk01/zone/:id' }, - // { title: 'channel', docs: 'https://docs.rsshub.app/traditional-media.html#xiang-gang-01', source: '/channel/:id/:title', target: '/hk01/channel/:id' }, - // { title: 'issue', docs: 'https://docs.rsshub.app/traditional-media.html#xiang-gang-01', source: '/issue/:id/:title', target: '/hk01/issue/:id' }, - // { title: 'tag', docs: 'https://docs.rsshub.app/traditional-media.html#xiang-gang-01', source: '/tag/:id/:title', target: '/hk01/tag/:id' }, - // ], - // }, - 'douban.com': { - _name: '豆瓣', - www: [ - { - title: '用户的广播', - docs: 'https://docs.rsshub.app/social-media.html#dou-ban', - source: '/people/:user/', - target: (params, url, document) => { - const uid = document && document.querySelector('html').innerHTML.match(/"id":"([0-9]+)"/)[1]; - return uid ? `/douban/people/${uid}/status` : ''; - }, - }, - { title: '小组-最新', docs: 'https://docs.rsshub.app/social-media.html#dou-ban', source: '/group/:groupid', target: '/douban/group/:groupid' }, - { title: '小组-最热', docs: 'https://docs.rsshub.app/social-media.html#dou-ban', source: '/group/:groupid', target: '/douban/group/:groupid/essence' }, - { title: '小组-精华', docs: 'https://docs.rsshub.app/social-media.html#dou-ban', source: '/group/:groupid', target: '/douban/group/:groupid/elite' }, - ], - }, - 'ems.com.cn': { _name: '中国邮政速递物流', www: [{ title: '新闻', docs: 'https://docs.rsshub.app/other.html#zhong-guo-you-zheng-su-di-wu-liu', source: '/aboutus/xin_wen_yu_shi_jian.html', target: '/ems/news' }] }, - 'popiapp.cn': { - _name: 'Popi 提问箱', - www: [ - { - title: '提问箱新回答', - docs: 'https://docs.rsshub.app/social-media.html#popi-ti-wen-xiang', - source: '/:id', - target: (params) => { - if (params.id) { - return '/popiask/:id'; - } - }, - }, - ], - }, - 'nppa.gov.cn': { - _name: '国家新闻出版署', - www: [ - { title: '栏目', docs: 'https://docs.rsshub.app/government.html#guo-jia-xin-wen-chu-ban-shu', source: '/nppa/channels/:channel', target: (params, url) => `/gov/nppa/${/nppa\/channels\/(\d+)\.shtml/.exec(url)[1]}` }, - { - title: '内容', - docs: 'https://docs.rsshub.app/government.html#guo-jia-xin-wen-chu-ban-shu', - source: '/nppa/contents/:channel/:content', - target: (params, url) => `/gov/nppa/${/nppa\/contents\/(\d+\/\d+)\.shtml/.exec(url)[1]}`, - }, - ], - }, - 'jjmhw.cc': { _name: '漫小肆', www: [{ title: '漫画更新', docs: 'https://docs.rsshub.app/anime.html#man-xiao-si', source: '/book/:id', target: '/manxiaosi/book/:id' }] }, - 'wenxuecity.com': { - _name: '文学城', - blog: [ - { title: '博客', docs: 'https://docs.rsshub.app/bbs.html#wen-xue-cheng-bo-ke', source: '/myblog/:id', target: '/wenxuecity/blog/:id' }, - { title: '博客', docs: 'https://docs.rsshub.app/bbs.html#wen-xue-cheng-bo-ke', source: '/myoverview/:id', target: '/wenxuecity/blog/:id' }, - ], - bbs: [ - { title: '最新主题', docs: 'https://docs.rsshub.app/bbs.html#wen-xue-cheng-zui-xin-zhu-ti', source: '/:cat', target: '/wenxuecity/bbs/:cat' }, - { title: '最新主题 - 精华区', docs: 'https://docs.rsshub.app/bbs.html#wen-xue-cheng-zui-xin-zhu-ti', source: '/:cat', target: '/wenxuecity/bbs/:cat/1' }, - { - title: '最热主题', - docs: 'https://docs.rsshub.app/bbs.html#wen-xue-cheng-zui-re-zhu-ti', - source: '/?cid=*', - target: (params, url, document) => { - const cid = document && new URL(document.location).searchParams.get('cid'); - return `/wenxuecity/hot/${cid}`; - }, - }, - ], - }, - 'buaq.net': { _name: '不安全资讯', '.': [{ title: '不安全资讯', docs: 'http://docs.rsshub.app/new-media.html#bu-an-quan', source: '/', target: '/buaq' }] }, - 'jian-ning.com': { _name: '建宁闲谈', '.': [{ title: '文章', docs: 'https://docs.rsshub.app/blog.html#jian-ning-xian-tan', source: '/*', target: '/blogs/jianning' }] }, - 'matataki.io': { - _name: 'matataki', - www: [ - { title: '最热作品', docs: 'https://docs.rsshub.app/new-media.html#matataki', source: '/article/', target: '/matataki/posts/hot' }, - { title: '最新作品', docs: 'https://docs.rsshub.app/new-media.html#matataki', source: '/article/latest', target: '/matataki/posts/latest' }, - { title: '作者创作', docs: 'https://docs.rsshub.app/new-media.html#matataki', source: '/user/:uid', target: (params) => `/matataki/users/${params.uid}/posts` }, - { title: 'Fan票关联作品', docs: 'https://docs.rsshub.app/new-media.html#matataki', source: ['/token/:tokenId', '/token/:tokenId/circle'], target: (params) => `/matataki/tokens/${params.tokenId}/posts` }, - { - title: '标签关联作品', - docs: 'https://docs.rsshub.app/new-media.html#matataki', - source: ['/tag/:tagId'], - target: (params, url) => { - const tagName = new URL(url).searchParams.get('name'); - return `/matataki/tags/${params.tagId}/${tagName}/posts`; - }, - }, - { title: '收藏夹', docs: 'https://docs.rsshub.app/new-media.html#matataki', source: '/user/:uid/favlist/:fid', target: (params) => `/matataki/users/${params.uid}/favorites/${params.fid}/posts` }, - ], - }, - 'eventernote.com': { _name: 'Eventernote', www: [{ title: '声优活动及演唱会', docs: 'https://docs.rsshub.app/anime.html#eventernote', source: '/actors/:name/:id/events', target: '/eventernote/actors/:name/:id' }] }, - 'instagram.com': { - _name: 'Instagram', - www: [ - { - title: '用户', - docs: 'https://docs.rsshub.app/social-media.html#instagram', - source: '/:id', - target: (params) => { - if (params.id !== 'explore' && params.id !== 'developer') { - return '/instagram/user/:id'; - } - }, - }, - ], - }, - 'huya.com': { _name: '虎牙直播', '.': [{ title: '直播间开播', docs: 'https://docs.rsshub.app/live.html#hu-ya-zhi-bo-zhi-bo-jian-kai-bo', source: '/:id', target: '/huya/live/:id' }] }, - 'craigslist.org': { _name: 'Craigslist', '.': [{ title: '商品搜索列表', docs: 'https://docs.rsshub.app/shopping.html#craigslist' }] }, - 'scboy.com': { - _name: 'scboy 论坛', - www: [ - { - title: '帖子', - docs: 'https://docs.rsshub.app/bbs.html#scboy', - source: '', - target: (params, url) => { - const id = url.includes('thread') ? url.split('-')[1].split('.')[0] : ''; - return id ? `/scboy/thread/${id}` : ''; - }, - }, - ], - }, - 'cqut.edu.cn': { - _name: '重庆理工大学', - tz: [{ title: '通知', docs: 'https://docs.rsshub.app/university.html#chong-qing-li-gong-da-xue', source: '/*' }], - lib: [{ title: '图书馆通知', docs: 'https://docs.rsshub.app/university.html#chong-qing-li-gong-da-xue', source: '/*' }], - }, - 'cqwu.net': { - _name: '重庆文理学院', - www: [ - { - title: '通知', - docs: 'https://docs.rsshub.app/university.html#chong-qing-wen-li-xue-yuan', - source: '/:type', - target: (params) => { - if (params.type === 'channel_7721.html') { - return '/cqwu/news/notify'; - } - }, - }, - { - title: '学术活动', - docs: 'https://docs.rsshub.app/university.html#chong-qing-wen-li-xue-yuan', - source: '/:type', - target: (params) => { - if (params.type === 'channel_7722.html') { - return '/cqwu/news/academiceve'; - } - }, - }, - ], - }, - 'trakt.tv': { - _name: 'Trakt.tv', - '.': [ - { - title: '用户收藏', - docs: 'https://docs.rsshub.app/multimedia.html#trakt-tv-yong-hu-shou-cang', - source: ['/users/:username/collection/:type/added', '/users/:username/collection'], - target: (params) => `/trakt/collection/${params.username}/${params.type || 'all'}`, - }, - ], - }, - 'furaffinity.net': { - _name: 'Fur Affinity', - www: [ - { title: '主页', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/', target: '/furaffinity/home' }, - { title: '浏览', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/browse/', target: '/furaffinity/browse' }, - { title: '站点状态', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/', target: '/furaffinity/status' }, - { - title: '搜索', - docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', - source: '/search/', - target: (params, url) => { - const keyword = new URL(url).searchParams.get('q'); - if (keyword) { - return `/furaffinity/search/${keyword}`; - } - }, - }, - { title: '用户主页简介', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/user/:username/', target: '/furaffinity/user/:username' }, - { title: '用户关注列表', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/watchlist/by/:username/', target: '/furaffinity/watching/:username' }, - { title: '用户被关注列表', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/watchlist/to/:username/', target: '/furaffinity/watchers/:username' }, - { title: '用户接受委托信息', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/commissions/:username/', target: '/furaffinity/commissions/:username' }, - { title: '用户的 Shouts 留言', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/user/:username/', target: '/furaffinity/shouts/:username' }, - { title: '用户的日记', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/journals/:username/', target: '/furaffinity/journals/:username' }, - { title: '用户的创作画廊', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/gallery/:username/', target: '/furaffinity/gallery/:username' }, - { title: '用户非正式作品', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/scraps/:username/', target: '/furaffinity/scraps/:username' }, - { title: '用户的喜爱列表', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/favorites/:username/', target: '/furaffinity/favorites/:username' }, - { title: '作品评论区', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/view/:id/', target: '/furaffinity/submission_comments/:id' }, - { title: '日记评论区', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/journal/:id/', target: '/furaffinity/journal_comments/:id' }, - ], - }, - 'e-hentai.org/': { - _name: 'E-Hentai', - '.': [ - { title: '收藏', docs: 'https://docs.rsshub.app/picture.html#ehentai', source: '/favorites.php', target: '/ehentai/favorites' }, - { title: '标签', docs: 'https://docs.rsshub.app/picture.html#ehentai', source: '/tag/:tag', target: '/ehentai/tag/:tag' }, - { - title: '搜索', - docs: 'https://docs.rsshub.app/picture.html#ehentai', - source: '/', - target: (params, url) => { - const keyword = new URL(url).searchParams.toString(); - if (keyword) { - return `/ehentai/search/${keyword}`; - } - }, - }, - ], - }, - 'iyingdi.com': { - _name: '旅法师营地', - www: [ - { title: '分区', docs: 'https://docs.rsshub.app/game.html#lv-fa-shi-ying-di', source: '/tz/tag/:tag', target: '/lfsyd/tag/:tag' }, - { title: '用户发帖', docs: 'https://docs.rsshub.app/game.html#lv-fa-shi-ying-di', source: ['/tz/people/:id', '/tz/people/:id/*'], target: '/lfsyd/user/:id' }, - ], - mob: [{ title: '分区', docs: 'https://docs.rsshub.app/game.html#lv-fa-shi-ying-di', source: '/fine/:tag', target: '/lfsyd/tag/:tag' }], - }, - 'macwk.com': { _name: 'MacWk', '.': [{ title: '应用更新', docs: 'https://docs.rsshub.app/program-update.html#macwk', source: '/soft/:name', target: '/macwk/soft/:name' }] }, - // 'zyshow.net': { www: [{ title: '', docs: 'https://docs.rsshub.app/game.html#lv-fa-shi-ying-di', source: '/:name/', target: '/zyshow/:name' }] }, - 'foreverblog.cn': { - _name: 'foreverblog', - www: [ - { - title: '十年之约', - docs: 'https://docs.rsshub.app/social-media.html#foreverblog', - }, - ], - }, -}); diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 00000000000000..a3092abe5333c5 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], +}; diff --git a/docker-compose.yml b/docker-compose.yml index 41fe95a71e0409..8b79ddf8085c28 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3' - services: rsshub: # two ways to enable puppeteer: @@ -8,29 +6,45 @@ services: image: diygod/rsshub restart: always ports: - - '1200:1200' + - "1200:1200" environment: NODE_ENV: production CACHE_TYPE: redis - REDIS_URL: 'redis://redis:6379/' - PUPPETEER_WS_ENDPOINT: 'ws://browserless:3000' # marked + REDIS_URL: "redis://redis:6379/" + PUPPETEER_WS_ENDPOINT: "ws://browserless:3000" # marked + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:1200/healthz"] + interval: 30s + timeout: 10s + retries: 3 depends_on: - redis - - browserless # marked + - browserless # marked - browserless: # marked - image: browserless/chrome # marked - restart: always # marked - ulimits: # marked - core: # marked - hard: 0 # marked - soft: 0 # marked + browserless: # marked + image: browserless/chrome # marked + restart: always # marked + ulimits: # marked + core: # marked + hard: 0 # marked + soft: 0 # marked + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/pressure"] + interval: 30s + timeout: 10s + retries: 3 redis: image: redis:alpine restart: always volumes: - redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 5s volumes: redis-data: diff --git a/docs/.format/chineseFormat.js b/docs/.format/chineseFormat.js deleted file mode 100644 index f15ec1e310afd4..00000000000000 --- a/docs/.format/chineseFormat.js +++ /dev/null @@ -1,32 +0,0 @@ -const file = require('./file'); -const width = require('string-width'); -const remark = require('remark'); -const pangu = require('remark-pangu'); -const frontmatter = require('remark-frontmatter'); -const stringify = require('remark-stringify'); -const gfm = require('remark-gfm'); -const prettier = require('remark-preset-prettier'); - -module.exports = { - rules: (list) => list.filter((e) => e.lang === file.LANG_CN), - handler: async (doc) => { - let result = await remark() - .use(frontmatter) - .use(pangu, { - inlineCode: false, - link: false, - }) - .use(stringify, { - bullet: '-', - ruleSpaces: true, - }) - .use(prettier) - .use(gfm, { - options: { - stringLength: width, - }, - }) - .process(doc); - return typeof result === 'string' ? result : typeof result.contents === 'string' ? result.contents : result.result; - }, -}; diff --git a/docs/.format/file.js b/docs/.format/file.js deleted file mode 100644 index 8e5516561b7d6b..00000000000000 --- a/docs/.format/file.js +++ /dev/null @@ -1,11 +0,0 @@ -const fs = require('fs'); - -module.exports = { - ROUTE_TYPE: 'route', - GUIDE_TYPE: 'guide', - NAV_TYPE: 'nav', - LANG_CN: 'zh-CN', - LANG_EN: 'en-US', - readFile: async (filePath) => fs.promises.readFile(filePath, { encoding: 'utf8' }), - writeFile: async (filePath, data) => fs.promises.writeFile(filePath, data, { encoding: 'utf8' }), -}; diff --git a/docs/.format/format.js b/docs/.format/format.js deleted file mode 100644 index 5c870db0e139c5..00000000000000 --- a/docs/.format/format.js +++ /dev/null @@ -1,126 +0,0 @@ -const file = require('./file'); -const sgf = require('staged-git-files'); -const path = require('path'); -const sortByHeading = require('./sortByHeading'); -const chineseFormat = require('./chineseFormat'); -const util = require('util'); -const exec = util.promisify(require('child_process').exec); - -/** - * Processors are objects contains two methods: - * `rules(list)`, and `handler(str)` - * rules filters required file document object - * and handler get document string and return formatted document - */ -const processors = [sortByHeading, chineseFormat]; - -// Helpers -const loopSideBar = (children, type, lang, prefix) => - children - .filter((e) => e !== '') - .map((x) => ({ - path: path.resolve(__dirname, '..', prefix, `./${x}.md`), - type, - lang, - })); -const loopNav = (nav, lang) => - nav.flatMap((e) => { - if (e.items) { - return loopNav(e.items, lang); - } - if (e.link.endsWith('/')) { - return { - path: path.resolve(__dirname, '..', e.link.slice(1), 'README.md'), - type: file.NAV_TYPE, - lang, - }; - } else { - return { - path: path.resolve(__dirname, '..', `${e.link.replace(/^\//, '')}.md`), - type: file.NAV_TYPE, - lang, - }; - } - }); -const loopType = (sidebar, lang, prefix) => loopSideBar(sidebar[0].children, file.GUIDE_TYPE, lang, prefix).concat(loopSideBar(sidebar[1].children, file.ROUTE_TYPE, lang, prefix)); - -/** - * Iterate config and build document object: - * E.g. - * { - path: 'docs/en/other.md', <-- full path here - type: 'route', <--- Defined in file.js - lang: 'en' <-- Defined in file.js - } - */ -const buildFileList = async () => { - const config = require(`../.vuepress/config`); - let fileList = []; - Object.keys(config.themeConfig.locales).forEach((key) => { - const locale = config.themeConfig.locales[key]; - const key_path = key.slice(1); - if (locale.hasOwnProperty('sidebar')) { - fileList = fileList.concat(loopType(locale.sidebar[key], locale.lang, key_path)); - } - if (locale.hasOwnProperty('nav')) { - fileList = fileList.concat(loopNav(locale.nav, locale.lang)); - } - }); - - return fileList; -}; - -/** - * Select files that only being modified - * Same format as `buildFileList()` - */ -const buildStagedList = async () => { - const stagedFiles = await sgf(); - const stagedFileList = []; - stagedFiles.forEach((e) => { - if (e.filename.endsWith('.md')) { - stagedFileList.push(e.filename); - } - }); - const fullFileList = await buildFileList(); - const result = []; - stagedFileList.forEach((e) => { - const f = fullFileList.find((x) => x.path.indexOf(e) !== -1); - if (f) { - result.push(f); - } - }); - - return result; -}; - -/** Entry - * Usage: node format.js --full/--staged - */ -(async () => { - // Mode - const flag = process.argv[2] || '--full'; - let fileList = []; - switch (flag) { - case '--staged': - fileList = await buildStagedList(); - break; - case '--full': - default: - fileList = await buildFileList(); - } - // console.log(fileList); - // return - - const stagedFiles = await sgf(); - for (const processor of processors) { - for (const e of processor.rules(fileList)) { - let formatted = await file.readFile(e.path); - formatted = await processor.handler(formatted); - await file.writeFile(e.path, formatted); - if (stagedFiles.find((x) => e.path.indexOf(x.filename) !== -1)) { - await exec(`git add ${e.path}`); - } - } - } -})(); diff --git a/docs/.format/md/hierarchySlug.js b/docs/.format/md/hierarchySlug.js deleted file mode 100644 index fdbbd9f2a6e084..00000000000000 --- a/docs/.format/md/hierarchySlug.js +++ /dev/null @@ -1,126 +0,0 @@ -// A fork of https://github.com/valeriangalliat/markdown-it-anchor -const slugify = (s) => encodeURIComponent(String(s).trim().toLowerCase().replace(/\s+/g, '-')); - -const position = { - false: 'push', - true: 'unshift', -}; - -const hasProp = Object.prototype.hasOwnProperty; - -const permalinkHref = (slug) => `#${slug}`; -const permalinkAttrs = (slug) => ({}); - -const renderPermalink = (slug, opts, state, idx) => { - const space = () => Object.assign(new state.Token('text', '', 0), { content: ' ' }); - - const linkTokens = [ - Object.assign(new state.Token('link_open', 'a', 1), { - attrs: [['class', opts.permalinkClass], ['href', opts.permalinkHref(slug, state)], ...Object.entries(opts.permalinkAttrs(slug, state))], - }), - Object.assign(new state.Token('html_block', '', 0), { - content: opts.permalinkSymbol, - }), - new state.Token('link_close', 'a', -1), - ]; - - // `push` or `unshift` according to position option. - // Space is at the opposite side. - if (opts.permalinkSpace) { - linkTokens[position[!opts.permalinkBefore]](space()); - } - state.tokens[idx + 1].children[position[opts.permalinkBefore]](...linkTokens); -}; - -const uniqueSlug = (slug, slugs, failOnNonUnique) => { - let uniq = slug; - let i = 2; - if (failOnNonUnique && hasProp.call(slugs, uniq)) { - throw Error(`User defined id attribute '${slug}' is NOT unique. Please fix it in your markdown to continue.`); - } else { - while (hasProp.call(slugs, uniq)) uniq = `${slug}-${i++}`; - } - slugs[uniq] = true; - return uniq; -}; - -const isLevelSelectedNumber = (selection) => (level) => level >= selection; -const isLevelSelectedArray = (selection) => (level) => selection.includes(level); - -const getTitle = (children) => children.filter((token) => token.type === 'text' || token.type === 'code_inline').reduce((acc, t) => acc + t.content, ''); - -const anchor = (md, opts) => { - opts = Object.assign({}, anchor.defaults, opts); - - md.core.ruler.push('anchor', (state) => { - const slugs = {}; - const tokens = state.tokens; - - const isLevelSelected = Array.isArray(opts.level) ? isLevelSelectedArray(opts.level) : isLevelSelectedNumber(opts.level); - - for (let i = 0; i < tokens.length; i++) { - const currentLevel = tokens[i].markup.length; - if (tokens[i].type === 'heading_open' && isLevelSelected(currentLevel)) { - const titleIndex = i + 1; - const currentTitle = getTitle(tokens[titleIndex].children); - let prevTitle = tokens[i].attrGet('id'); - let constructedTitle = prevTitle; - - // Skip if user has defined a id - if (typeof prevTitle !== 'string') { - if (prevTitle === null) { - prevTitle = []; - } - - let recurIndex = i + 2; - while (recurIndex < tokens.length && (tokens[recurIndex].type !== 'heading_open' || tokens[recurIndex].markup.length > currentLevel)) { - if (tokens[recurIndex].type === 'heading_open') { - let recurPrevTitle = tokens[recurIndex].attrGet('id'); - if (typeof recurPrevTitle !== 'string') { - if (recurPrevTitle === null) { - recurPrevTitle = []; - } - recurPrevTitle = [...recurPrevTitle, currentTitle]; - tokens[recurIndex].attrSet('id', recurPrevTitle); - } - } - recurIndex += 1; - } - - prevTitle.push(currentTitle); - //console.log(prevTitle); - - // @TODO maybe use previous slugified title so we don't need to calculate that much? - constructedTitle = prevTitle.join('-'); - //console.log(constructedTitle); - } - - const slug = uniqueSlug(opts.slugify(constructedTitle), slugs, true); - tokens[i].attrSet('id', slug); - - if (opts.permalink) { - opts.renderPermalink(slug, opts, state, i); - } - - if (opts.callback) { - opts.callback(tokens[i], { slug, constructedTitle }); - } - } - } - }); -}; - -anchor.defaults = { - level: 1, - slugify, - permalink: false, - renderPermalink, - permalinkClass: 'header-anchor', - permalinkSpace: true, - permalinkSymbol: '¶', - permalinkBefore: false, - permalinkHref, - permalinkAttrs, -}; - -module.exports = anchor; diff --git a/docs/.format/sortByHeading.js b/docs/.format/sortByHeading.js deleted file mode 100644 index 49ace2df8f3fe9..00000000000000 --- a/docs/.format/sortByHeading.js +++ /dev/null @@ -1,56 +0,0 @@ -const file = require('./file'); -const pinyinCompare = new Intl.Collator('zh-Hans-CN-u-co-pinyin').compare; -const isASCII = (str) => /^[\x00-\x7F]*$/.test(str); - -module.exports = { - rules: (list) => list.filter((e) => e.type === file.ROUTE_TYPE), - handler: async (data) => { - const content = data.split('\n'); - const blocks = []; - const h1 = []; - - let i = 0; - while (i < content.length) { - const m = /^##\s*(.*)$/.exec(content[i]); - if (m) { - const b = { - title: m[1], - content: [], - }; - - b.content.push(content[i]); - i++; - while (i < content.length && !/^##\s.*$/.test(content[i])) { - b.content.push(content[i]); - i++; - } - blocks.push(b); - } else { - h1.push(content[i]); - i++; - } - } - - let newContent = blocks - .sort((a, b) => { - const ia = isASCII(a.title[0]); - const ib = isASCII(b.title[0]); - if (ia && ib) { - return a.title.toLowerCase() < b.title.toLowerCase() ? -1 : 1; - } else if (ia || ib) { - return ia > ib ? -1 : 1; - } else { - return pinyinCompare(a.title, b.title); - } - }) - .map((x) => x.content.join('\n')) - .join('\n'); - if (newContent) { - h1.push(newContent); - } - - newContent = h1.join('\n'); - - return Promise.resolve(newContent); - }, -}; diff --git a/docs/.vuepress/components/Route.vue b/docs/.vuepress/components/Route.vue deleted file mode 100644 index ab413119168c08..00000000000000 --- a/docs/.vuepress/components/Route.vue +++ /dev/null @@ -1,98 +0,0 @@ - - diff --git a/docs/.vuepress/components/RouteEn.vue b/docs/.vuepress/components/RouteEn.vue deleted file mode 100644 index c8808787385f25..00000000000000 --- a/docs/.vuepress/components/RouteEn.vue +++ /dev/null @@ -1,98 +0,0 @@ - - diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js deleted file mode 100644 index a29df462feb873..00000000000000 --- a/docs/.vuepress/config.js +++ /dev/null @@ -1,182 +0,0 @@ -const { pinyin, PINYIN_STYLE } = require('@napi-rs/pinyin'); -const { slugify: _slugify } = require('@vuepress/shared-utils'); - -module.exports = { - plugins: { - '@vuepress/google-analytics': { - ga: 'UA-48084758-10', - }, - '@vuepress/pwa': { - serviceWorker: true, - updatePopup: { - '/': { - message: '发现新内容可用', - buttonText: '刷新', - }, - '/en/': { - message: 'New content is available', - buttonText: 'Refresh', - }, - }, - }, - '@vuepress/back-to-top': true, - sitemap: { - hostname: 'https://docs.rsshub.app', - }, - 'vuepress-plugin-meilisearch': { - hostUrl: 'https://meilisearch.rsshub.app', - apiKey: '375c36cd9573a2c1d1e536214158c37120fdd0ba6cd8829f7a848e940cc22245', - indexUid: 'rsshub', - maxSuggestions: 14, - }, - }, - locales: { - '/': { - lang: 'zh-CN', - title: 'RSSHub', - description: '🍰 万物皆可 RSS', - }, - '/en/': { - lang: 'en-US', - title: 'RSSHub', - description: '🍰 Everything is RSSible', - }, - }, - markdown: { - anchor: { - level: 999, // Disable original Plugin - }, - extendMarkdown: (md) => { - md.use(require('../.format/md/hierarchySlug'), { - slugify(s) { - return _slugify( - pinyin(s, { - style: PINYIN_STYLE.Plain, - heteronym: true, - segment: true, - }) - .map((item) => item[0]) - .join('-') - ); - }, - level: 2, - permalink: true, - permalinkBefore: true, - permalinkSymbol: '#', - }); - }, - }, - head: [ - ['meta', { name: 'viewport', content: 'width=device-width, initial-scale=1.0' }], - ['link', { rel: 'icon', href: '/logo.png' }], - ['link', { rel: 'manifest', href: '/manifest.json' }], - ['meta', { name: 'theme-color', content: '#fff' }], - ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }], - ['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' }], - ['link', { rel: 'apple-touch-icon', href: '/apple-touch-icon.png' }], - ['link', { rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#ff8549' }], - ], - theme: 'vuepress-theme-rsshub', - themeConfig: { - repo: 'DIYgod/RSSHub', - editLinks: true, - docsDir: 'docs', - smoothScroll: true, - locales: { - '/': { - lang: 'zh-CN', - selectText: 'Languages', - label: '简体中文', - editLinkText: '在 GitHub 上编辑此页', - lastUpdated: '上次更新', - nav: require('./nav/zh'), - sidebar: { - '/': [ - { - title: '指南', - collapsable: true, - children: ['', 'usage', 'faq', 'parameter', 'api'], - }, - { - title: '路由', - collapsable: false, - sidebarDepth: 1, - children: [ - 'social-media', - 'new-media', - 'traditional-media', - 'bbs', - 'blog', - 'programming', - 'design', - 'live', - 'multimedia', - 'picture', - 'anime', - 'program-update', - 'university', - 'forecast', - 'travel', - 'shopping', - 'game', - 'reading', - 'government', - 'study', - 'journal', - 'finance', - 'other', - ], - }, - ], - }, - }, - '/en/': { - lang: 'en-US', - selectText: '选择语言', - label: 'English', - editLinkText: 'Edit this page on GitHub', - lastUpdated: 'Last Updated', - nav: require('./nav/en'), - sidebar: { - '/en/': [ - { - title: 'Guide', - collapsable: true, - children: ['', 'usage', 'faq', 'parameter', 'api'], - }, - { - title: 'Routes', - collapsable: false, - sidebarDepth: 1, - children: [ - 'social-media', - 'new-media', - 'traditional-media', - 'bbs', - 'blog', - 'programming', - 'design', - 'live', - 'multimedia', - 'picture', - 'anime', - 'program-update', - 'university', - 'forecast', - 'travel', - 'shopping', - 'game', - 'reading', - 'government', - 'study', - 'journal', - 'finance', - 'other', - ], - }, - ], - }, - }, - }, - }, -}; diff --git a/docs/.vuepress/nav/en.js b/docs/.vuepress/nav/en.js deleted file mode 100644 index f27f7a096cca0d..00000000000000 --- a/docs/.vuepress/nav/en.js +++ /dev/null @@ -1,41 +0,0 @@ -module.exports = [ - { - text: 'Guide', - link: '/en/', - }, - { - text: 'Join Us', - ariaLabel: 'Join Us', - items: [ - { - text: 'Getting Started', - link: '/en/joinus/quick-start', - }, - { - text: 'More details', - items: [ - { - text: 'Script Standard', - link: '/en/joinus/script-standard', - }, - { - text: 'Date Handling', - link: '/en/joinus/pub-date', - }, - { - text: 'Use Cache', - link: '/en/joinus/use-cache', - }, - ], - }, - ], - }, - { - text: 'Deploy', - link: '/en/install/', - }, - { - text: 'Support RSSHub', - link: '/en/support/', - }, -]; diff --git a/docs/.vuepress/nav/zh.js b/docs/.vuepress/nav/zh.js deleted file mode 100644 index 1f46309ef86780..00000000000000 --- a/docs/.vuepress/nav/zh.js +++ /dev/null @@ -1,41 +0,0 @@ -module.exports = [ - { - text: '指南', - link: '/', - }, - { - text: '参与我们', - ariaLabel: '参与我们', - items: [ - { - text: '快速开始', - link: '/joinus/quick-start', - }, - { - text: '详细规范', - items: [ - { - text: '路由规范', - link: '/joinus/script-standard', - }, - { - text: '日期处理', - link: '/joinus/pub-date', - }, - { - text: '使用缓存', - link: '/joinus/use-cache', - }, - ], - }, - ], - }, - { - text: '部署', - link: '/install/', - }, - { - text: '支持 RSSHub', - link: '/support/', - }, -]; diff --git a/docs/.vuepress/public/_headers b/docs/.vuepress/public/_headers deleted file mode 100644 index fe11f60e7df2ec..00000000000000 --- a/docs/.vuepress/public/_headers +++ /dev/null @@ -1,2 +0,0 @@ -/* - cache-control: public, max-age=60 diff --git a/docs/.vuepress/public/android-chrome-192x192.png b/docs/.vuepress/public/android-chrome-192x192.png deleted file mode 100644 index 00030df0df738a..00000000000000 Binary files a/docs/.vuepress/public/android-chrome-192x192.png and /dev/null differ diff --git a/docs/.vuepress/public/android-chrome-384x384.png b/docs/.vuepress/public/android-chrome-384x384.png deleted file mode 100644 index 6256971a1b81d6..00000000000000 Binary files a/docs/.vuepress/public/android-chrome-384x384.png and /dev/null differ diff --git a/docs/.vuepress/public/apple-touch-icon.png b/docs/.vuepress/public/apple-touch-icon.png deleted file mode 100644 index 3aa4bddb9ef469..00000000000000 Binary files a/docs/.vuepress/public/apple-touch-icon.png and /dev/null differ diff --git a/docs/.vuepress/public/logo.png b/docs/.vuepress/public/logo.png deleted file mode 100644 index 3ca374f04abdee..00000000000000 Binary files a/docs/.vuepress/public/logo.png and /dev/null differ diff --git a/docs/.vuepress/public/manifest.json b/docs/.vuepress/public/manifest.json deleted file mode 100644 index 7f32fa2193c272..00000000000000 --- a/docs/.vuepress/public/manifest.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "RSSHub", - "short_name": "RSSHub", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/android-chrome-384x384.png", - "sizes": "384x384", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone", - "start_url": "/" -} diff --git a/docs/.vuepress/public/readable-douban.png b/docs/.vuepress/public/readable-douban.png deleted file mode 100644 index 727e3b2871bc28..00000000000000 Binary files a/docs/.vuepress/public/readable-douban.png and /dev/null differ diff --git a/docs/.vuepress/public/readable-twitter.png b/docs/.vuepress/public/readable-twitter.png deleted file mode 100644 index 2fa4c2af35e4fd..00000000000000 Binary files a/docs/.vuepress/public/readable-twitter.png and /dev/null differ diff --git a/docs/.vuepress/public/readable-weibo.png b/docs/.vuepress/public/readable-weibo.png deleted file mode 100644 index 56dc9104c342a6..00000000000000 Binary files a/docs/.vuepress/public/readable-weibo.png and /dev/null differ diff --git a/docs/.vuepress/public/safari-pinned-tab.svg b/docs/.vuepress/public/safari-pinned-tab.svg deleted file mode 100644 index 048215c453864f..00000000000000 --- a/docs/.vuepress/public/safari-pinned-tab.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - -Created by potrace 1.11, written by Peter Selinger 2001-2013 - - - - - diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl deleted file mode 100644 index 1640a6aaadf451..00000000000000 --- a/docs/.vuepress/styles/index.styl +++ /dev/null @@ -1,69 +0,0 @@ -.navbar .home-link .site-name { - color: #F5712C; -} - -.page .custom-block.tip { - border-color: #F5712C; -} - -.theme-container .page .content__default .logo-img { - margin-top: 3.6rem; -} - -.page .content__default .logo-text { - padding-top: 2.6rem; - padding-bottom: 2rem; -} - -.icon.outbound.outbound { - display: none; -} - -a { - word-break: break-all; -} - -#jie-shao { - display: none; -} - -#introduction { - display: none; -} - -#app .global-ui .sw-update-popup { - border: 1px solid #F5712C; -} - -h3 { - color: $accentColor; -} - -.sidebar-sub-headers a.sidebar-link { - color: #F5712C; -} - -// for Route and RouteEn components -li.params p { - display: inline; - } - -.routeBlock { - margin: 1rem 0 2rem; -} - -.routeBlock .example > * { - vertical-align: middle; -} - -.routeBlock .example img { - margin-left: 5px; -} - -#app .page .badge.tip { - background-color: #FFB74D; -} - -#app .page .badge.warn { - background-color: #ff774d; -} diff --git a/docs/.vuepress/styles/palette.styl b/docs/.vuepress/styles/palette.styl deleted file mode 100644 index 56eb5f7799f4e1..00000000000000 --- a/docs/.vuepress/styles/palette.styl +++ /dev/null @@ -1 +0,0 @@ -$accentColor = #F5712C diff --git a/docs/.vuepress/theme/components/CarbonAds.vue b/docs/.vuepress/theme/components/CarbonAds.vue deleted file mode 100644 index 240a4e8a1b7712..00000000000000 --- a/docs/.vuepress/theme/components/CarbonAds.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - diff --git a/docs/.vuepress/theme/index.js b/docs/.vuepress/theme/index.js deleted file mode 100644 index a85dc0efa0d1cc..00000000000000 --- a/docs/.vuepress/theme/index.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - extend: '@vuepress/theme-default', -}; diff --git a/docs/.vuepress/theme/layouts/Layout.vue b/docs/.vuepress/theme/layouts/Layout.vue deleted file mode 100644 index 9463595b99aed5..00000000000000 --- a/docs/.vuepress/theme/layouts/Layout.vue +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/docs/.vuepress/theme/package.json b/docs/.vuepress/theme/package.json deleted file mode 100644 index e52477405f4931..00000000000000 --- a/docs/.vuepress/theme/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "vuepress-theme-rsshub", - "main": "index.js" -} diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 1b6d12a11c76b0..00000000000000 --- a/docs/README.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -sidebarDepth: 0 ---- - -# 介绍 - -

- RSSHub -

-

RSSHub

- -> 🍰 万物皆可 RSS - -[![telegram](https://img.shields.io/badge/chat-telegram-brightgreen.svg?logo=telegram\&style=flat-square)](https://t.me/rsshub) -[![npm publish](https://img.shields.io/github/workflow/status/DIYgod/RSSHub/publish/master?label=npm%20publish\&logo=npm\&style=flat-square)](https://www.npmjs.com/package/rsshub) -[![docker publish](https://img.shields.io/github/workflow/status/DIYgod/RSSHub/%5Bdocker%5D%20CI%20for%20releases/master?label=docker%20publish\&logo=docker\&style=flat-square)](https://hub.docker.com/r/diygod/rsshub) -[![test](https://img.shields.io/github/workflow/status/DIYgod/RSSHub/test/master?label=test\&logo=github\&style=flat-square)](https://github.com/DIYgod/RSSHub/actions/workflows/test.yml?query=event%3Apush+branch%3Amaster) -[![Test coverage](https://img.shields.io/codecov/c/github/DIYgod/RSSHub.svg?style=flat-square\&logo=codecov)](https://app.codecov.io/gh/DIYgod/RSSHub/branch/master) - -RSSHub 是一个开源、简单易用、易于扩展的 RSS 生成器,可以给任何奇奇怪怪的内容生成 RSS 订阅源。RSSHub 借助于开源社区的力量快速发展中,目前已适配数百家网站的上千项内容 - -可以配合浏览器扩展 [RSSHub Radar](https://github.com/DIYgod/RSSHub-Radar) 和 移动端辅助 App [RSSBud](https://github.com/Cay-Zhang/RSSBud) (iOS) 与 [RSSAid](https://github.com/LeetaoGoooo/RSSAid) (Android) 食用 - -[Telegram 群](https://t.me/rsshub) | [Telegram 频道](https://t.me/awesomeRSSHub) - -## 鸣谢 - -### Special Sponsors - -RSS3 - -### Sponsors - -[Sayori Studio](https://t.me/SayoriStudio) . [Sion Kazama](https://blog.sion.moe) . [琚致远](https://www.shaoyaoju.org/) . [Rolly RSS 阅读器](https://www.coolapk.com/apk/239500) . [mokeyjay](https://www.mokeyjay.com/) . [萌开源联盟](https://www.moeunion.com) . [hooke007](https://github.com/hooke007/MPV_lazy) . [feeds.pub](https://feeds.pub) . [KINGX@安全引擎](http://cve.today/) - -[![](https://opencollective.com/static/images/become_sponsor.svg)](/support/) - -### Contributors - -[![](https://opencollective.com/RSSHub/contributors.svg?width=740)](https://github.com/DIYgod/RSSHub/graphs/contributors) - -Logo designer [sheldonrrr](https://dribbble.com/sheldonrrr) - -### Backers - - - -## 相关项目 - -- [RSSHub Radar](https://github.com/DIYgod/RSSHub-Radar) | 一个可以帮助你快速发现和订阅当前网站 RSS 和 RSSHub 的浏览器扩展 -- [RSSBud](https://github.com/Cay-Zhang/RSSBud) ([TestFlight 公测](https://testflight.apple.com/join/rjCVzzHP)) | iOS 平台的 RSSHub Radar,专为移动生态优化 -- [RSSAid](https://github.com/LeetaoGoooo/RSSAid) | 基于 Flutter 构建的 Android 平台的 RSSHub Radar -- [DocSearch](https://github.com/Fatpandac/DocSearch) | Link RSSHub DocSearch into Raycast diff --git a/docs/anime.md b/docs/anime.md deleted file mode 100644 index 8232fb643b35f3..00000000000000 --- a/docs/anime.md +++ /dev/null @@ -1,695 +0,0 @@ ---- -pageClass: routes ---- - -# 二次元 - -## 005.tv - -### 二次元资讯 - - - -## 18comic 禁漫天堂 - -### 成人 A 漫 - - - -分类 - -| 全部 | 其他漫畫 | 同人 | 韓漫 | 美漫 | 短篇 | 單本 | -| --- | ------- | ------ | ------ | ------ | ----- | ------ | -| all | another | doujin | hanman | meiman | short | single | - -时间范围 - -| 全部 | 今天 | 这周 | 本月 | -| -- | -- | -- | -- | -| a | t | w | m | - -排列顺序 - -| 最新 | 最多点阅 | 最多图片 | 最爱 | -| -- | ---- | ---- | -- | -| mr | mv | mp | tf | - -关键字(供参考) - -| YAOI | 女性向 | NTR | 非 H | 3D | 獵奇 | -| ---- | --- | --- | --- | -- | -- | - - - -### 搜索 - - - -::: tip 提示 - -关键字必须超过两个字,这是来自网站的限制。 - -::: - - - -### 专辑 - - - -::: tip 提示 - -专辑 id 不包括 URL 中标题的部分。 - -::: - - - -### 文庫 - - - -分类 - -| 全部 | 紳夜食堂 | 遊戲文庫 | JG GAMES | 模型山下 | -| -- | ------ | ------- | -------- | ------ | -| | dinner | raiders | jg | figure | - - - -## 1draw #深夜の真剣お絵描き 60 分一本勝負 - -### 投稿一览 - - - -## 78 动漫 - -### 新品速递 - - - -::: tip 提示 - -若订阅 [新品速递](https://www.78dm.net/news),网址为 。截取 `https://www.78dm.net` 到末尾的部分 `/news` 作为参数,此时路由为 [`/78dm/news`](https://rsshub.app/78dm/news)。 - -若订阅子分类 [新品速递 - 综合](https://www.78dm.net/news/0/9/0/0/0/0/0/1.html),网址为 。截取 `https://www.78dm.net` 到末尾 `.html` 的部分 `/news/0/9/0/0/0/0/0/1` 作为参数,路由为 [`/78dm/news/0/9/0/0/0/0/0/1`](https://rsshub.app/78dm/news/0/9/0/0/0/0/0/1)。 - -::: - - - -### 精彩评测 - - - -::: tip 提示 - -若订阅 [精彩评测](https://www.78dm.net/eval_list),网址为 。截取 `https://www.78dm.net` 到末尾的部分 `/eval_list` 作为参数,此时路由为 [`/78dm/eval_list`](https://rsshub.app/78dm/eval_list)。 - -若订阅子分类 [精彩评测 - 综合](https://www.78dm.net/eval_list/120/0/0/1.html),网址为 。截取 `https://www.78dm.net` 到末尾 `.html` 的部分 `/eval_list/120/0/0/1` 作为参数,路由为 [`/78dm/eval_list/120/0/0/1`](https://rsshub.app/78dm/eval_list/120/0/0/1)。 - -::: - - - -### 好帖推荐 - - - -::: tip 提示 - -若订阅 [好帖推荐](https://www.78dm.net/ht_list),网址为 。截取 `https://www.78dm.net` 到末尾的部分 `/ht_list` 作为参数,此时路由为 [`/78dm/ht_list`](https://rsshub.app/78dm/ht_list)。 - -若订阅子分类 [好帖推荐 - 综合](https://www.78dm.net/ht_list/107/0/0/1.html),网址为 。截取 `https://www.78dm.net` 到末尾 `.html` 的部分 `/ht_list/107/0/0/1` 作为参数,路由为 [`/78dm/ht_list/107/0/0/1`](https://rsshub.app/78dm/ht_list/107/0/0/1)。 - -::: - - - -## AcFun - -### 番剧 - - - -::: tip 提示 - -番剧 id 不包含开头的 aa。 -例如: 的番剧 id 是 5022158,不包括开头的 aa。 - -::: - -### 用户投稿 - - - -### 文章 - - - -| 二次元画师 | 综合 | 生活情感 | 游戏 | 动漫文化 | 漫画文学 | -| ----- | --- | ---- | --- | ---- | ---- | -| 184 | 110 | 73 | 164 | 74 | 75 | - -| 最新发表 | 最新动态 | 最热文章 | -| ---------- | --------------- | -------- | -| createTime | lastCommentTime | hotScore | - -| 时间不限 | 24 小时 | 三天 | 一周 | 一个月 | -| ---- | ------ | -------- | ------- | -------- | -| all | oneDay | threeDay | oneWeek | oneMonth | - - - -## AGE 动漫 - -### 最近更新 - - - -### 番剧详情 - - - -## Anime1 - -### 動畫 - - - -时间和动画名称请自己从网址取得: `https://anime1.me/category/2018年秋季/刀劍神域-alicization` - - - -### 搜尋 - - - -## Animen 动漫平台 - -### news - - - -| 最新 | 焦点 | 动画 | 漫画 | 游戏 | 小说 | 真人版 | 活动 | 音乐 | 访谈 | 其他 | 新闻稿 | 懒人包 | 公告 | -| -- | -- | -- | -- | -- | -- | --- | -- | -- | -- | -- | --- | --- | -- | -| zx | jd | dh | mh | yx | xs | zrb | hd | yy | ft | qt | xwg | lrb | gg | - - - -## Anitama - -### Anitama Channel - - - -## Bangumi 番组计划 - -### 放送列表 - - - -### 条目的章节 - - - -### 条目的吐槽箱 - - - -### 条目的评论 - - - -### 条目的讨论 - - - -### 现实人物的新作品 - - - -### 小组话题的新回复 - - - -### 小组话题 - - - -### 用户日志 - - - -## bilibili - -见 [#bilibili](/social-media.html#bilibili) - -## CCC 創作集 - -### 漫畫 - - - -## DLsite - -### 当前日期发售的新产品 - - - -| 同人 | 漫画 | 软件 | 同人 (R18) | 漫画 (R18) | 美少女游戏 | 乙女 | BL | -| ---- | ----- | ---- | -------- | -------- | ----- | ----- | -- | -| home | comic | soft | maniax | books | pro | girls | bl | - - - -### 产品打折信息 - - - - -## ebb.io - -### ebb - - - -## Eventernote - -### 声优活动及演唱会 - - - -## Hanime.tv - -### 最近更新 - - - -## Hpoi 手办维基 - -### 情报 - - - -分类 - -| 全部 | 手办 | 模型 | -| --- | ----- | ----- | -| all | hobby | model | - - - -### 浏览周边 - - - -| 角色手办 | 作品手办 | -| --------- | ----- | -| charactar | works | - - - -### 用户动态 - - - -| 想买 | 预定 | 已入 | 关注 | 有过 | -| ---- | -------- | --- | ---- | ------ | -| want | preorder | buy | care | resell | - - - -## iwara - -### 用户 - - - -| type | 视频 | 图片 | -| :--: | :---: | :---: | -| 参数 | video | image | - - - -### 用户订阅列表 - - - -::: warning 注意 - -用户动态需要 iwara 登录后的 Cookie 值,所以只能自建,详情见[部署页面](/install/#route-specific-configurations)的配置模块。 - -::: - - - -## Kemono - -### Posts - - - -Sources - -| Posts | Patreon | Pixiv Fanbox | Gumroad | SubscribeStar | DLsite | Discord | Fantia | -| ----- | ------- | ------------ | ------- | ------------- | ------ | ------- | ------ | -| posts | patreon | fanbox | gumroad | subscribestar | dlsite | discord | fantia | - -::: tip 提示 - -当选择 `posts` 作为参数 **source** 的值时,参数 **id** 不生效。 - -::: - - - -## lovelive-anime - -### Love Live 官网最新 News - - - -### Love Live 官网分类 Topics - - - -| 子企划名(非全称) | Lovelive! | Lovelive! Sunshine!! | Lovelive! Nijigasaki High School Idol Club | Lovelive! Superstar!! | -| --------- | ----------- | -------------------- | ------------------------------------------ | --------------------- | -| `abbr`参数 | otonokizaka | uranohoshi | nijigasaki | yuigaoka | - -| 分类名 | 全てのニュース | 音楽商品 | アニメ映像商品 | キャスト映像商品 | 劇場 | アニメ放送 / 配信 | キャスト配信 / ラジオ | ライブ / イベント | ブック | グッズ | ゲーム | メディア | ご当地情報 | その他 | キャンペーン | -| ------------ | ------------ | ----- | ----------- | ---------- | ------- | ---------- | ------------ | ---------- | ----- | ----- | ---- | ----- | ----- | ----- | -------- | -| `category`参数 | *无参数* | music | anime_movie | cast_movie | theater | onair | radio | event | books | goods | game | media | local | other | campaign | - - - -## Mox.moe - -### 首頁 - - - -::: tip 提示 - -在首页将分类参数选择确定后跳转到的分类页面 URL 中,`/l/` 后的字段即为分类参数。 - -如 [科幻 + 日語 + 日本 + 長篇 + 完結 + 最近更新](https://mox.moe/l/CAT%2A科幻,日本,完結,lastupdate,jpn,l,BL) 的 URL 为 [https://mox.moe/l/CAT%2A 科幻,日本,完結,lastupdate,jpn,l,BL](https://mox.moe/l/CAT%2A科幻,日本,完結,lastupdate,jpn,l,BL),此时 `/l/` 后的字段为 `CAT%2A科幻,日本,完結,lastupdate,jpn,l,BL`。最终获得路由为 [`/mox/CAT%2A科幻,日本,完結,lastupdate,jpn,l,BL`](https://rsshub.app/mox/CAT%2A科幻,日本,完結,lastupdate,jpn,l,BL) - -::: - - - -## say 花火 - -### 文章 - - - -## THBWiki - -### 日历 - - - -## Vol.moe - -### vol - - - -| 连载 | 完结 | -| ------ | ------ | -| serial | finish | - - - -## Webtoons - -### 漫画更新 - - - -比如漫画公主彻夜未眠的网址为, 则`lang=zh-hant`,`category=drama`,`name=gongzhucheyeweimian`,`id=894`. - -### [Naver](https://comic.naver.com) - - - -## X 漫画 - -### 最新动态 - - - -## 俺の 3D エロ動画 (oreno3d) - -::: tip 提示 - -可配合其他 RSS 解析库 (如`Python`的`feedparser`库) 实现视频的更新检测以及自动下载 - -::: - -### 关键词搜索 - - - -| 高評価 | 急上昇 | 新着 | 人気 | -| --------- | --- | ------ | ---------- | -| favorites | hot | latest | popularity | - - - -### 角色搜索 - - - -### 作者搜索 - - - -### 标签搜索 - - - -### 原作搜索 - - - -## 包子漫画 - -#### 订阅漫画 - - - -## 嘀哩嘀哩 - dilidili - -### 嘀哩嘀哩番剧更新 - - - -请打开对应番剧的纵览页 (非具体某集), 从 url 中最后一位查看番剧 id.(一般为英文) -除去 ' 海贼 ' 此类具有特殊页面的超长番剧,绝大多数页面都可以解析. -最适合用来追新番 - - - -## 電撃オンライン - -### 最新記事 - - - -| All | PlayStation | Nintendo | Xbox | PC | Girl’sStyle | Arcade Web | App | Anime | Review | Rank | -| --- | ----------- | -------- | --------- | --- | ----------- | ---------- | --- | ----- | ------ | ---- | -| | dps | nintendo | microsoft | dpc | gstyle | arcade | app | anime | review | rank | - - - -## 东方我乐多丛志 - -### 文章 - - - -语言 - -| 中文 | 日文 | 韩文 | -| -- | -- | -- | -| cn | ja | ko | - -类型 - -| 最新情报 | 连载 | 特辑 | 小说 | 漫画 | 新闻 | -| ----- | ------ | ---------- | ------ | ------ | ---- | -| index | series | interviews | novels | comics | news | - -| 音乐点评 | 游戏测评 | 同人作品感想 | 关于本站 | -| ------------ | ----------- | ----------- | ------------- | -| music_review | game_review | book_review | where_are_you | - -**注:** 最新情报包括后面所有类型的文章,内容较多,谨慎使用。 - - - -## 咚漫 - -### 漫画更新 - - - -## 動畫瘋 - -### 最後更新 - - - -### 動畫 - - - -## 動漫狂 - -### 漫画更新 - - - -## 风之动漫 - -### 风之动漫 - - - -## 海猫吧 - -### 漫画更新 - - - -## 看漫画 - -### 漫画更新 - - - -### 漫画个人订阅 - - - -::: tip 提示 - -个人订阅需要自建 -环境变量需要添加 MHGUI_COOKIE - -::: - - - -### 镜像站 - 漫画更新 - - - -### 台湾站 - 漫画更新 - - - -## 拷贝漫画 - -### 漫画更新 - - - -## 漫画 DB - -### 漫画 DB - - - -## 漫画堆 - -### 漫画 - - - -## 漫小肆 - -### 漫画更新 - - - -## 萌番组 - -### 最新 - - - -### 标签 - - - -更多标签请前往 [搜索种子](https://bangumi.moe/search/index) - - - -## 三界异次元 - -### 三界异次元 - - - -## 紳士漫畫 - -### 最新 - - - -### 分类更新 - - - -## 鼠绘漫画 - -### 鼠绘漫画 - - - -## 腾讯动漫 - -### 排行榜 - - - -| 月票榜 | 飙升榜 | 新作榜 | 畅销榜 | TOP100 | 男生榜 | 女生榜 | -| --- | ---- | --- | --- | ------ | ---- | ------ | -| mt | rise | new | pay | top | male | female | - -::: tip 提示 - -`time` 参数仅在 `type` 参数选为 **月票榜** 的时候生效。 - -::: - - - -### 漫画 - - - -## 忧郁的 loli - -### 文章 - - - -## 终点分享 - -### 最新汉化 - - - -## アニメ新番組 - -### 當季新番 - - diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index 376fec8a4ae819..00000000000000 --- a/docs/api.md +++ /dev/null @@ -1,62 +0,0 @@ -# API - -::: warning 注意 -API 仍处于开发状态中, 并可能会有改动。欢迎提供建议! -::: - -RSSHub 提供下列 API: - -## 可用公共路由列表 - -::: tip 提示 -`protected_router.js`下的路由**不会被**包含在此 API 返回的结果当中. -::: - -举例: - -路由: `/api/routes/:name?` - -参数: - -- `name`, 路由一级名称,对应 中的文件夹名称。可选,**缺省则返回所有可用路由**. - -成功请求将会返回 HTTP 状态码 `200 OK` 与 JSON 结果,格式如下: - -```js -{ - "status": "success", - "data": { - "bilibili": { - "routes": [ - "/bilibili/user/video/:uid", - "/bilibili/user/article/:uid", - "/bilibili/user/fav/:uid", - "/bilibili/user/coin/:uid", - "/bilibili/user/dynamic/:uid", - "/bilibili/user/followers/:uid", - "/bilibili/user/followings/:uid", - "/bilibili/user/channel/collections/:uid/:sid", - "/bilibili/user/channel/series/:uid/:sid", - "/bilibili/partion/:tid", - "/bilibili/partion/ranking/:tid/:days?", - "/bilibili/bangumi/:seasonid", - "/bilibili/video/page/:aid", - "/bilibili/video/reply/:aid", - "/bilibili/link/news/:product", - "/bilibili/live/room/:roomID", - "/bilibili/live/search/:key/:order", - "/bilibili/live/area/:areaID/:order", - "/bilibili/fav/:uid/:fid", - "/bilibili/blackboard", - "/bilibili/mall/new", - "/bilibili/mall/ip/:id", - "/bilibili/ranking/:rid?/:day?", - "/bilibili/topic/:topic" - ] - } - }, - "message": "request returned 22 routes" -} -``` - -若无符合请求路由,请求将会返回 HTTP 状态码 `204 No Content`. diff --git a/docs/bbs.md b/docs/bbs.md deleted file mode 100644 index 0e98eb3afc54d0..00000000000000 --- a/docs/bbs.md +++ /dev/null @@ -1,1130 +0,0 @@ ---- -pageClass: routes ---- - -# 论坛 - -## 19 楼 - -### 头条 - - - -| 杭州 | 台州 | 嘉兴 | 宁波 | 湖州 | -| --- | ------- | ------- | ------ | ------ | -| www | taizhou | jiaxing | ningbo | huzhou | - -| 绍兴 | 湖州 | 温州 | 金华 | 舟山 | -| -------- | ------ | ------- | ------ | -------- | -| shaoxing | huzhou | wenzhou | jinhua | zhoushan | - -| 衢州 | 丽水 | 义乌 | 萧山 | 余杭 | -| ------ | ------ | ---- | -------- | ------ | -| quzhou | lishui | yiwu | xiaoshan | yuhang | - -| 临安 | 富阳 | 桐庐 | 建德 | 淳安 | -| ----- | ------ | ------ | ------ | ------ | -| linan | fuyang | tonglu | jiande | chunan | - - - -## 2047 - -### 分类 - - - -分类 - -| 首页 | 时政 | 民生 | 科技 | 休闲 | -| -- | -------- | ----- | ---- | ------- | -| | opinions | daily | stem | culture | - -| 欢乐 | 江湖 | 站务 | 水 | -| -------- | ----- | ------ | ----- | -| tainment | inner | office | water | - -| 时事 | 观点 | 政治 | 人物 | 司法实践 | -| -- | -- | -- | -- | ---- | -| 2 | 1 | 31 | 10 | 49 | - -| 香港 | 历史 | 疫情 | 新疆 | 假设 | -| -- | -- | -- | -- | -- | -| 47 | 85 | 44 | 32 | 22 | - -| 经济 | 生活 | 留学移民 | 情感 | 教育 | -| -- | -- | ---- | -- | -- | -| 65 | 41 | 14 | 23 | 66 | - -| 技术 | 翻墙 | 加密技术 | 哲学 | 阅读 | -| -- | -- | ---- | -- | -- | -| 3 | 18 | 24 | 34 | 6 | - -| 音乐 | 影视 | 炉边诗社 | 博客 | 美食 | -| -- | -- | ---- | -- | -- | -| 7 | 11 | 46 | 8 | 43 | - -| 文学 | ACG | 欢乐 | 公告 | 分享发现 | -| -- | --- | -- | -- | ---- | -| 84 | 30 | 17 | 67 | 5 | - -| 分享原创 | 2049 | 宗教 | 语言 | 人文 | -| ---- | ---- | -- | -- | -- | -| 12 | 16 | 42 | 56 | 76 | - -| 站务 | 国防 | 工会 | 水 | 江湖 | -| -- | -- | -- | - | -- | -| 13 | 15 | 29 | 4 | 21 | - -| 吐槽 | 树洞 | 标本 | -| -- | -- | -- | -| 9 | 19 | 20 | - -排序 - -| 即时 | 新帖 | 综合 | 精华 | 高赞 | 观看 | -| --- | --- | ---- | ----- | --- | -- | -| t_u | t_c | t_hn | t_hn2 | amv | vc | - - - -## 423Down - -### 分类 - - - -| category | 全部 | -| :------: | :-: | -| index | all | - -| category | 安卓软件 | -| :------: | :--: | -| android | apk | - -| category | 原创软件 | 媒体播放 | 网页浏览 | 图形图像 | 聊天软件 | 办公软件 | 上传下载 | 系统辅助 | 系统必备 | 安全软件 | 补丁相关 | 硬件相关 | -| :------: | :----------: | :--------: | :-----: | :---: | :--: | :--: | :--: | :--------: | :--------: | :------: | :---: | :------: | -| computer | originalsoft | multimedia | browser | image | im | work | down | systemsoft | systemplus | security | patch | hardware | - -| category | windows 11 | windows 10 | windows 7 | windows xp | windows pe | -| :------: | :--------: | :--------: | :-------: | :--------: | :--------: | -| os | win11 | win10 | win7 | winxp | winpe | - - - -## Chiphell - -### 子版块 - - - -## Citavi 中文网站论坛 - - - -| 全部 | 下载安装 | 许可证 | 入门指南 | 升级更新 | 教程 | 新闻资讯 | 技巧分享 | 账户插件 | 其他 | | -| -- | ---------- | ------- | -------------- | ------ | -------------- | ---- | ----- | ------------- | ------ | ------ | -| | Installing | License | GettingStarted | Update | CitaviinDetail | News | Share | CitaviAccount | Addons | Others | - - - -## Dcard - -::: warning 注意 - -僅能透過台灣 IP 抓取。 - -::: - -### 首頁帖子 - - - -### 板塊帖子 - - - -## Discuz - -### 通用子版块 - 自动检测 - - - -### 通用子版块 - 指定版本 - - - -| Discuz X 系列 | Discuz 7.x 系列 | -| ----------- | ------------- | -| x | 7 | - - - -### 通用子版块 - 支持 Cookie - - - -| Discuz X 系列 | Discuz 7.x 系列 | -| ----------- | ------------- | -| x | 7 | - - - -## Elastic 中文社区 - -### 发现 - - - -如 [Elasticsearch 最新](https://elasticsearch.cn/category-2) 的 URL 为 ,则分类参数处填写 `category-2`,最后得到路由地址 [`/elasticsearch-cn/category-2`](https://rsshub.app/elasticsearch-cn/category-2)。 - -又如 [求职招聘 30 天热门](https://elasticsearch.cn/sort_type-hot\_\_\_\_category-12\__day-30) 的 URL 为 ,则分类参数处填写 `sort_type-hot____category-12__day-30`,最后得到路由地址 [`/elasticsearch-cn/sort_type-hot____category-12__day-30`](https://rsshub.app/elasticsearch-cn/sort_type-hot\_\_\_\_category-12\__day-30)。 - - - -## eTOLAND - -### 主题贴 - - - -## HACKER TALK 黑客说 - -### 最新帖子 - - - -## LearnKu - -### 社区 - - - -| 招聘 | 翻译 | 问答 | 链接 | -| ---- | ------------ | -- | ----- | -| jobs | translations | qa | links | - -## LowEndTalk - -### Discussion - - - -## MCBBS - -### 版块 - - - -### 帖子 - - - -## Meteor - -### 看板 - - - -### 看板列表 - - - -## Mobilism - -### 论坛 - - - -| 安卓 | iPhone | iPad | -| ------- | ------ | ---- | -| android | iphone | ipad | - -| 应用 | 游戏 | -| ---- | ----- | -| apps | games | - - - -### 门户 - - - -| 安卓应用 | 安卓游戏 | 图书 | iPad 应用 | iPad 游戏 | iPhone 应用 | iPhone 游戏 | -| ---- | ----- | ----- | ------- | ------- | --------- | --------- | -| aapp | agame | ebook | ipapp | ipgame | iapp | igame | - - - -## NGA - -### 分区帖子 - - - -### 帖子 - - - -## PLAYNO.1 玩樂達人 - -### AV - -::: warning 注意 - -目前观测到该博客可能禁止日本 IP 访问。建议部署在日本区以外的服务器上。 - -::: - - - -| 全部文章 | AV 新聞 | AV 導覽 | -| ---- | ----- | ----- | -| 78 | 3 | 5 | - - - -### 情趣 - - - -| 全部文章 | 情趣體驗報告 | 情趣新聞 | 情趣研究所 | -| ---- | ---------- | ---- | -------- | -| all | experience | news | graduate | - - - -## RF 技术社区 - -### 文章 - - - -## Ruby China - -> 未登录状态下抓取页面非实时更新 - -### 主题 - - - -| 主题类型 | type | -| ---- | ---------- | -| 精华贴 | excellent | -| 优质帖子 | popular | -| 无人问津 | no_reply | -| 最新回复 | last_reply | -| 最新发布 | last | - -### 招聘 - - - -## Saraba1st - -### 帖子 - - - -帖子网址如果为 那么帖子 id 就是 `1789863`。 - - - -## SCBOY 论坛 - -### 帖子 - - - -帖子网址如果为 那么帖子 tid 就是 `1789863`。 - -访问水区需要添加环境变量 `SCBOY_BBS_TOKEN`, 详情见部署页面的配置模块。 `SCBOY_BBS_TOKEN`在 cookies 的`bbs_token`中。 - - - -## The Ring of Wonder - -### 首页更新 - - - - - -## V2EX - -### 最热 / 最新主题 - - - -### 帖子 - - - -### 标签 - - - -## X 岛匿名版 - -### 串 - - - -| 综合线 | 非创作线 | 综合版 1 | 欢乐恶搞 | 买买买 (剁手) | 数码 (装机) | 技术 (码农) | 科学 (干货) | 二创 (画师) | 电影 / 电视 | ROLL 点 | -| --- | ---- | ----- | ---- | -------- | ------- | ------- | ------- | ------- | ------- | ------ | -| 1 | 3 | 综合版 1 | 欢乐恶搞 | 买买买 | 数码 | 技术宅 | 科学 | 二创 | 影视 | ROLL 点 | - -| 动画 | 漫画 | 主播 (UP) | 特摄 | 胶佬 (手办) | 小马 (美漫) | 东方 Project | 舰娘 | VOCALOID | -| -- | -- | ------- | -- | ------- | ------- | ---------- | -- | -------- | -| 动画 | 漫画 | 主播 | 特摄 | 胶佬 | 小马 | 东方 Project | 舰娘 | VOCALOID | - -| 游戏综合版 | 手游 | 卡牌桌游 | 任天堂 NS | LOL | SE(FF14) | DOTA & 自走棋 | 联机 (服务器发布) | 怪物猎人 | 鹰角游戏 | 米哈游 | 音乐游戏 | -| ----- | -- | ---- | ------ | --- | -------- | ---------- | ---------------- | ---- | ---- | --- | ---- | -| 游戏 | 手游 | 卡牌桌游 | 任天堂 | LOL | SE | DOTA | 联机 %28 服务器发布 %29 | 怪物猎人 | 鹰角游戏 | 米哈游 | 音游 | - -| 跑团 | 规则怪谈 | 都市怪谈 (灵异) | 脑洞 (推理) | 料理 (美食) | 宠物 | 学业 (校园) | 社畜 | 育儿 | 摄影 (cos) | 文学 (推书) | 音乐 (推歌) | 技术支持 | -| -- | ---- | --------- | ------- | ------- | -- | ------- | -- | -- | ------------ | ------- | ------- | ---- | -| 跑团 | 规则怪谈 | 都市怪谈 | 推理 | 料理 | 宠物 | 考试 | 社畜 | 育儿 | 摄影 %28cos%29 | 文学 | 音乐 | 技术支持 | - - - -## ZodGame - -### 论坛版块 - - - -## Zuvio - -### 校園話題 - - - -### 看板列表 - - - -## 巴哈姆特電玩資訊站 - -### 熱門推薦 - - - -## 百度贴吧 - -### 帖子列表 - - - -### 精品帖子 - - - -### 帖子动态 - - - -### 楼主动态 - - - -### 用户帖子 - - - -用户 ID 可以通过打开用户的主页后查看地址栏的 `un` 字段来获取。 - - - -## 才符 - -### 用户动态 - - - -### 驿站帖子 - - - -## 超理论坛 - -### 板块 - - - -| 数学 | 物理 | 化学 | 生物 | 天文 | 技术 | 管理 | 公告 | -| ---- | ------- | ---- | ------- | ----- | ---- | ----- | ------ | -| math | physics | chem | biology | astro | tech | admin | announ | - -| 其他 | 语言 | 社科 | 科幻 | 辑录 | -| ------ | ---- | ------ | ------ | ----------- | -| others | lang | socsci | sci-fi | collections | - - - -## 第一会所 - -### 子版块 - - - -## 电鸭社区 - -### 工作机会 - - - -## 斗鱼 - -### 鱼吧帖子 - - - -| 回复时间排序 | 发布时间排序 | -| ------ | ------ | -| 1 | 2 | - - - -### 鱼吧跟帖 - - - -## 恩山无线论坛 - -### 板块 - - - -## 二次元虫洞 - -### 板块 - - - -板块(更多板块请自行 [查看](http://www.2cycd.com)) - -| 音乐下载(默认) | 动漫下载 | 游戏下载 | -| -------- | ---- | ---- | -| 43 | 53 | 42 | - -排序 - -| 发布时间排序(默认) | 回复/查看 | 查看 | -| ---------- | ------- | ----- | -| dateline | replies | views | - - - -## 光谷社区 - -### 子论坛 - - - -| 首页 | 你问我答 | 同城活动 | IT 技术 | 金融财经 | 创业创客 | 城市建设 | -| -- | ---- | -------- | ----- | ------- | ------- | ---- | -| | qna | lowshine | it | finance | startup | city | - - - -## 海角社区 - -### 热门 - - - -### 新闻 - - - -### 大事记 - - - -### 原创 - - - -### 精华 - - - -### 公告 - - - -### 最新 - - - -### 文章 - - - -| 默认 | 最新 | 热门 | 精华 | 悬赏 | 出售 | -| -- | -- | -- | -- | -- | -- | -| 0 | 1 | 2 | 3 | 4 | 5 | - - - -## 虎扑 - -### 首页 - - - -| NBA | CBA | 足球 | -| --- | --- | ------ | -| nba | cba | soccer | - -::: tip 提示 - -电竞分类参见 [游戏热帖](https://bbs.hupu.com/all-gg) 的对应路由 [`/hupu/all/all-gg`](https://rsshub.app/hupu/all/all-gg)。 - -::: - - - -### 社区 - - - -::: tip 提示 - -更多社区参见 [社区](https://bbs.hupu.com) - -::: - - - -### 热帖 - - - -::: tip 提示 - -更多热帖版面参见 [论坛](https://bbs.hupu.com) - -::: - - - -## 华为心声社区 - -### 华为家事 - - - -分区 ID - -| 全部帖子 | 公司文件 | 管理思考 | 产品改进 | 版务公告 | -| ---- | ---- | ---- | ---- | ---- | -| | 155 | 415 | 427 | 419 | - -排序方式 - -| 最新发帖 | 最新回复 | 最多回复 | 最多点击 | -| ----- | ----- | ---------- | --------- | -| cTime | rTime | replycount | viewcount | - - - -## 集思录 - -### 广场 - - - -分类 - -| 全部 | 债券 / 可转债 | 基金 | 套利 | 新股 | -| -- | -------- | -- | -- | -- | -| | 4 | 7 | 5 | 3 | - -排序 - -| 最新 | 热门 | 按发表时间 | -| -- | --- | -------- | -| | hot | add_time | - -几天内 - -| 30 天 | 7 天 | 当天 | -| ---- | --- | -- | -| 30 | 7 | 1 | - - - -### 用户回复 - - - -### 用户主题 - - - -## 看雪 - -### 论坛 - - - -| 版块 | category | -| ---------- | ---------- | -| 智能设备 | iot | -| 区块链安全 | blockchain | -| Android 安全 | android | -| iOS 安全 | ios | -| 软件逆向 | re | -| 编程技术 | coding | -| 加壳脱壳 | unpack | -| 密码算法 | crypto | -| 二进制漏洞 | vuln | -| CrackMe | crackme | -| Pwn | pwn | -| WEB 安全 | web | -| 外文翻译 | translate | -| 全站 | all | - -| 类型 | type | -| ---- | ------ | -| 最新主题 | latest | -| 精华主题 | digest | - -## 梨园 - -### 主题帖(全站) - - - -### 主题帖(板块) - - - -### 主题帖(专题) - - - -### 主题帖(用户) - - - -## 龙空 - -### 分区 - - - -### 帖子 - - - -## 龙腾网 - -### 网帖翻译 - - - -| 最新 | 科技 | 娱乐 | 文化 | 社会 | 体育 | 历史 | 趣闻 | 图说世界 | -| ------ | ---------- | ----- | ------- | --------- | ----- | ------- | ----------- | ------- | -| latest | technology | funny | culture | community | sport | history | curiosities | picture | - - - -## 牛客网 - -### 面经 - - - -可选参数: - -- companyId:公司 id,[🔗查询链接](https://www.nowcoder.com/discuss/tag/exp), 复制打开 -- order:3 - 最新;1 - 最热 -- phaseId:0 - 所有;1 - 校招;2 - 实习;3 - 社招 - - - -### 讨论区 - - - -| 最新回复 | 最新发表 | 最新 | 精华 | -| ---- | ---- | -- | -- | -| 0 | 3 | 1 | 4 | - - - -### 校招日程 - - - -### 求职推荐 - - - -### 实习广场 & 社招广场 - - - -可选城市有:北京、上海、广州、深圳、杭州、南京、成都、厦门、武汉、西安、长沙、哈尔滨、合肥、其他 - -职位类型代码见下表: - -| 研发 | 测试 | 数据 | 算法 | 前端 | 产品 | 运营 | 其他 | -| -- | -- | -- | -- | -- | -- | -- | -- | -| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 0 | - -排序参数见下表: - -| 最新发布 | 最快处理 | 处理率最高 | -| ---- | ---- | ----- | -| 1 | 2 | 3 | - - - -## 品葱 - -### 发现 - - - -| 最新 | 推荐 | 热门 | -| --- | --------- | --- | -| new | recommend | hot | - -### 精选 - - - -### 话题 - - - -## 三星盖乐世社区 - -### 最新帖子 - - - -## 书友社区 - -### 导读 - - - -| 最新发表 | 最新热门 | 最新精华 | 最新回复 | -| --------- | ---- | ------ | ---- | -| newthread | hot | digest | new | - - - -## 水木社区 - -### 分区文章 - - - -| 社区管理 | 国内院校 | 休闲娱乐 | 五湖四海 | 游戏运动 | 社会信息 | 知性感性 | 文化人文 | 学术科学 | 电脑技术 | -| --------- | ---------- | ------------- | -------- | ---- | ------- | ------- | ------- | ------- | ---------- | -| community | university | entertainment | location | game | society | romance | culture | science | technology | - - - -### 用户文章 - - - -## 天涯论坛 - -### 子版块 - - - -### 用户帖子 - - - -### 用户的回帖 - - - -## 通信人家园 - -### 论坛 频道 - - - -| 最新 500 个主题帖 | 最新 500 个回复帖 | 最新精华帖 | 最新精华帖 | 一周热帖 | 本月热帖 | -| :---------: | :---------: | :---: | :---: | :--: | :--: | -| 1 | 2 | 3 | 4 | 5 | 6 | - - - -## 万维读者 - -### 焦点新闻 - - - -## 威锋 - -### 社区 - - - -| 最新回复 | 最新发布 | 热门 | 精华 | -| ------ | ---- | --- | ------- | -| newest | all | hot | essence | - - - -## 文学城 - -### 博客 - - - -### 最热主题 - - - -### 最新主题 - - - -### 焦点新闻 - - - -## 小米社区 - -### 圈子 - - - -## 小木虫论坛 - -### 期刊点评 - - - -| SCI 期刊 | 中文期刊 | -| ------ | ---- | -| | cn | - -### 分类 - - - -::: tip 提示 - -尚不支持需要登录访问的版块 - -::: - -网络生活区 - -| 休闲灌水 | 虫友互识 | 文学芳草园 | 育儿交流 | 竞技体育 | 有奖起名 | 有奖问答 | 健康生活 | -| ---- | ---- | ----- | ---- | ---- | ---- | ---- | ---- | -| 6 | 133 | 166 | 359 | 377 | 408 | 69 | 179 | - -科研生活区 - -| 硕博家园 | 教师之家 | 博后之家 | English Cafe | 职场人生 | 专业外语 | 外语学习 | 导师招生 | 找工作 | 招聘信息布告栏 | 考研 | 考博 | 公务员考试 | -| ---- | ---- | ---- | ------------ | ---- | ---- | ---- | ---- | --- | ------- | --- | --- | ----- | -| 198 | 199 | 342 | 328 | 405 | 432 | 126 | 430 | 185 | 346 | 127 | 197 | 280 | - -学术交流区 - -| 论文投稿 | SCI 期刊点评 | 中文期刊点评 | 论文道贺祈福 | 论文翻译 | 基金申请 | 学术会议 | 会议与征稿布告栏 | -| ---- | -------- | ------ | ------ | ---- | ---- | ---- | -------- | -| 125 | 见期刊路由 | 见期刊路由 | 307 | 278 | 234 | 299 | 345 | - -出国留学区 - -| 留学生活 | 公派出国 | 访问学者 | 海外博后 | 留学 DIY | 签证指南 | 出国考试 | 海外院所点评 | 海外校友录 | 海归之家 | -| ---- | ---- | ---- | ---- | ------ | ---- | ---- | ------ | ----- | ---- | -| 336 | 131 | 386 | 385 | 334 | 335 | 337 | 399 | 见院校路由 | 428 | - -化学化工区 - -| 有机交流 | 有机资源 | 高分子 | 无机 / 物化 | 分析 | 催化 | 工艺技术 | 化工设备 | 石油化工 | 精细化工 | 电化学 | 环境 | SciFinder/Reaxys | -| ---- | ---- | --- | ------- | --- | --- | ---- | ---- | ---- | ---- | --- | --- | ---------------- | -| 189 | 325 | 236 | 170 | 238 | 190 | 373 | 374 | 212 | 227 | 263 | 230 | 343 | - -材料区 - -| 材料综合 | 材料工程 | 微米和纳米 | 晶体 | 金属 | 无机非金属 | 生物材料 | 功能材料 | 复合材料 | -| ---- | ---- | ----- | --- | --- | ----- | ---- | ---- | ---- | -| 378 | 379 | 233 | 262 | 301 | 213 | 286 | 364 | 365 | - -计算模拟区 - -| 第一性原理 | 量子化学 | 计算模拟 | 分子模拟 | 仿真模拟 | 程序语言 | -| ----- | ---- | ---- | ---- | ---- | ---- | -| 291 | 290 | 279 | 322 | 292 | 312 | - -生物医药区 - -| 新药研发 | 药学 | 药品生产 | 分子生物 | 微生物 | 动植物 | 生物科学 | 医学 | -| ---- | --- | ---- | ---- | --- | --- | ---- | --- | -| 192 | 148 | 429 | 366 | 367 | 368 | 144 | 142 | - -人文经济区 - -| 金融投资 | 人文社科 | 管理学 | 经济学 | -| ---- | ---- | --- | --- | -| 272 | 453 | 447 | 446 | - -专业学科区 - -| 数理科学综合 | 机械 | 物理 | 数学 | 农林 | 食品 | 地学 | 能源 | 信息科学 | 土木建筑 | 航空航天 | 转基因 | -| ------ | --- | --- | --- | --- | --- | --- | --- | ---- | ---- | ---- | --- | -| 452 | 370 | 228 | 323 | 371 | 207 | 261 | 372 | 145 | 147 | 434 | 438 | - -注册执考区 - -| 化环类执考 | 医药类考试 | 土建类考试 | 经管类考试 | 其他类执考 | -| ----- | ----- | ----- | ----- | ----- | -| 414 | 417 | 418 | 415 | 419 | - -文献求助区 - -| 文献求助 | 外文书籍求助 | 标准与专利 | 检索知识 | 代理 Proxy 资源 | -| ---- | ------ | ----- | ---- | ----------- | -| 158 | 219 | 226 | 130 | 203 | - -资源共享区 - -| 电脑软件 | 手机资源 | 科研工具 | 科研资料 | 课件资源 | 试题资源 | 资源求助 | 电脑使用 | -| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | -| 55 | 410 | 188 | 300 | 112 | 380 | 401 | 347 | - -科研市场区 - -| 课堂列表 | 综合广告 | 试剂耗材抗体 | 仪器设备 | 测试定制合成 | 技术服务 | 留学服务 | 教育培训 | 个人求购专版 | 个人转让专版 | QQ 群 / 公众号专版 | 手机红包 | 金币购物 | -| ---- | ---- | ------ | ---- | ------ | ---- | ---- | ---- | ------ | ------ | ------------ | ---- | ---- | -| 454 | 284 | 390 | 389 | 392 | 396 | 350 | 394 | 316 | 436 | 362 | 302 | 460 | - -论坛事务区 - -| 木虫讲堂 | 论坛更新日志 | 论坛公告发布 | 我来提意见 | 版主交流 | 规章制度 | 论坛使用帮助 (只读) | 我与小木虫的故事 | -| ---- | ------ | ------ | ----- | ---- | ---- | ----------- | -------- | -| 468 | 437 | 5 | 321 | 134 | 317 | 215 | 376 | - -版块孵化区 - -| 版块工场 | -| ---- | -| myf | - - - -## 一亩三分地 - -### 帖子 - - - -| 热门帖子 | 最新帖子 | -| ---- | ---- | -| hot | new | - -### 用户主题帖 - - - -### 用户回帖 - - - -### 录取结果 - - -::: warning 三个 id 获取方式 - -1. 打开 -2. 打开控制台 -3. 切换到 Network 面板 -4. 点击 搜索 按钮 -5. 点击 results?ps=15\&pg=1 POST 请求 -6. 找到 Request Payload 请求参数,例如 filters: {planyr: "13", planmajor: "1", outname_w: "ACADIAU"} ,则三个 id 分别为: 13,1,ACADIAU - -::: - - - -### 博客 - - - -| 分类 | 分类名 | -| ------- | ---------------------------------------- | -| 全部 | | -| 一亩三分地 | 一亩三分地 | -| 论坛精华 | 一亩三分地 - 论坛精华 | -| 咨询服务 | 咨询服务 | -| 学校院系 | 学校院系信息 | -| 找工求职 | 如何找工作 | -| 美国经济 | 如何找工作 - 美国经济与就业 | -| 杂谈其他 | 其他类别 | -| 抄袭 | 其他类别 - 抄袭 | -| 直播 | 其他类别 - 直播 | -| 热门专业 | eecsmis 统计金工等热门专业 | -| EECSMIS | eecsmis 统计金工等热门专业 - eecsmis 专业 | -| 数据科学 | eecsmis 统计金工等热门专业 - 数据科学 | -| 统计金工 | eecsmis 统计金工等热门专业 - 生物统计金融工程公共健康生物技术制药行业 | -| 留学申请 | 留学申请信息 | -| GT 考试 | 留学申请信息 - gt 考试 | -| 定位 | 留学申请信息 - 定位 | -| 文书写作 | 留学申请信息 - 文书写作 | -| 面试 | 留学申请信息 - 面试 | -| 移民绿卡 | 移民办绿卡 | -| 美国学习 | 美国学习 | -| 美国生活 | 美国生活 | - - - -## 音频应用 - -### 最新主题 - - - -## 直播吧 - -### 子论坛 - - - -### 回帖 - - - -### 滚动新闻 - - - -| NBA | 足球 | 电竞 | 综合 | -| --- | ----- | -------- | ------ | -| nba | zuqiu | dianjing | zonghe | - - - -## 中国灵异网 - -### 分类 - - - -| 编辑推荐 | 奇闻异事 | 鬼话连篇 | -| ------- | ---------- | -------------- | -| tuijian | qiwenyishi | guihualianpian | - -| 灵异事件 | 灵异图片 | 民间奇谈 | -| ------------- | ------------ | ------------ | -| lingyishijain | lingyitupian | minjianqitan | - - diff --git a/docs/blog.md b/docs/blog.md deleted file mode 100644 index 93b7976df98a74..00000000000000 --- a/docs/blog.md +++ /dev/null @@ -1,357 +0,0 @@ ---- -pageClass: routes ---- - -# 博客 - -## archdaily - -### 首页 - - - -## Benedict Evans - - - -## CSDN - -### 用户博客 - - - -## Google Sites - -### 文章更新 - - - -### 文章最近改动 - - - -## Gwern Branwen - -### 博客 - - - -## hashnode - -### 用户博客 - - - -::: tip 提示 - -username 为博主用户名,而非`xxx.hashnode.dev`中`xxx`所代表的 blog 地址。 - -::: - - -## Hedwig.pub - -### 博客 - - - -| 呆唯的 Newsletter | 0neSe7en 的技术周刊 | 地心引力 | 宪学宪卖 | Comeet 每周精选 | 无鸡之谈 | 我有一片芝麻地 | -| -------------- | -------------- | ------ | ------ | ----------- | -------- | ------- | -| hirasawayui | se7en | walnut | themez | comeet | sunskyxh | zmd | - -> 原则上只要是 {type}.hedwig.pub 都可以匹配。 - -## Hexo - -### Next 主题博客 - - - -### Yilia 主题博客 - - - -### Fluid 主题博客 - - - -## Hi, DIYgod - -### DIYgod 的动森日记 - - - -### DIYgod 的可爱的手办们 - - - -## JustRun - -### JustRun - - - -## LaTeX 开源小屋 - -### 首页 - - - -## LeeMeng - -### blog - - - -## Miris Whispers - -### 博客 - - - -## Paul Graham 博客 - -通过提取文章全文,提供比官方源更佳的阅读体验。 - -### Essays - - - -## Phrack Magazine - -### 文章 - - - -## Polkadot - -### 博客 - - - -## PolkaWorld - -### 最新资讯 - - - -::: tip 提示 - -在路由末尾处加上 `?limit=限制获取数目` 来限制获取条目数量,默认值为`10`. - -::: - -## Stratechery by Ben Thompson - -### 博客 - - - -## Whoscall - -### 最新文章 - - - -### 分類 - - - -| News | Whoscall 百科 | 防詐小學堂 | Whoscall 日常 | -| ------ | ------------- | --------- | ------------- | -| 1-News | 5-Whoscall 百科 | 4 - 防詐小學堂 | 6-Whoscall 日常 | - - - -### 標籤 - - - -| 防疫也防詐 | 防詐專家 | 來電辨識 | whoscall 日常 | -| ----- | ---- | ---- | ----------- | - - - -## WordPress - -### 博客 - - - -## yuzu emulator - -### Entry - - - -## 阿里云系统组技术博客 - -### 首页 - - - -## 财新博客 - -### 用户博客 - - - -通过提取文章全文,以提供比官方源更佳的阅读体验. - - - -## 大侠阿木 - -### 首页 - - - -## 大眼仔旭 - -### 分类 - - - -| 微软应用 | 安卓应用 | 教程资源 | 其他资源 | -| ------- | ------- | -------- | ----- | -| windows | android | tutorial | other | - - - -## 華康字型故事 - -### 博客 - - - -## 黄健宏博客 - -### 文章 - - - -## 建宁闲谈 - -### 文章 - - - -## 劍心.回憶 - -### 分类 - - - -::: tip 提示 - -如 `藝能新聞` 的 `日劇新聞` 分类,路由为 `/jnews/news_drama` - -::: - -藝能新聞 jnews - -| 日劇新聞 | 日影新聞 | 日樂新聞 | 日藝新聞 | -| ---------- | ---------- | ---------- | ------------------ | -| news_drama | news_movie | news_music | news_entertainment | - -| 動漫新聞 | 藝人美照 | 清涼寫真 | 日本廣告 | 其他日聞 | -| -------- | ------------ | ---------- | ---- | ----------- | -| news_acg | artist-photo | photoalbum | jpcm | news_others | - -旅遊情報 jpnews - -| 日本美食情報 | 日本甜點情報 | 日本零食情報 | 日本飲品情報 | 日本景點情報 | -| ----------- | ------------- | ------------- | ------------- | ------------------ | -| jpnews-food | jpnews-sweets | jpnews-okashi | jpnews-drinks | jpnews-attractions | - -| 日本玩樂情報 | 日本住宿情報 | 日本活動情報 | 日本購物情報 | 日本社會情報 | -| ----------- | ------------ | ------------- | --------------- | -------------- | -| jpnews-play | jpnews-hotel | jpnews-events | jpnews-shopping | jpnews-society | - -| 日本交通情報 | 日本天氣情報 | -| -------------- | -------------- | -| jpnews-traffic | jpnews-weather | - -日劇世界 jdrama - -| 每周劇評 | 日劇總評 | 資料情報 | -| ------------------- | ------------------ | ---------- | -| drama_review_weekly | drama_review_final | drama_data | - -| 深度日劇 | 收視報告 | 日劇專欄 | 劇迷互動 | -| ---------- | ------------ | ------------ | ----------------- | -| drama_deep | drama_rating | drama_column | drama_interactive | - - - -## 敬维博客 - -### 文章 - - - -## 零博客 - -### 分类 - - - -| muitinⒾ | aidemnⒾ | srettaⓂ | qⓅ | sucoⓋ | -| ------- | ------- | ------- | -- | ----- | -| initium | inmedia | matters | pq | vocus | - - - -## 每日安全 - -### 推送 - - - -## 美团技术团队 - -### 最近更新 - - - -## 十年之约 - -### 文章 - - - -## 王五四文集 - -### 文章 - - - -## 王垠博客 - -### 文章 - - - -## 雨苁博客 - -### 首页 - - - -### 分类 - - - -## 竹白 - -### 文章 - - - -::: tip 提示 - -在路由末尾处加上 `?limit=限制获取数目` 来限制获取条目数量,默认值为`20` - -::: - - diff --git a/docs/design.md b/docs/design.md deleted file mode 100644 index 61195acd61fede..00000000000000 --- a/docs/design.md +++ /dev/null @@ -1,413 +0,0 @@ ---- -pageClass: routes ---- - -# 设计 - -## Axis Studios - -### Work type - - - -文章内 Work type 指向的栏目地址,比如: 的 tag 为 `full-service-cg-production`,要注意的是 tag 和文章的目录是一样的。 - -有一些 tag 并不经常使用: `Script`, `direction`, `production`, `design-concept` 等等。 - - - -## Behance - -### 用户作品 - - - -Behance 用户主页 URL 获取用户名,如 的用户名为 `mishapetrick`。 - - - -## Blow Studio - -### 主页 - - - -## Blur Studio - -### Works - - - -## Digic Picture - -### 作品和新闻 - - - -## Dribbble - -### 流行 - - - -### 用户(团队) - - - -### 关键词 - - - -## Eagle - -### 博客 - - - -| 全部 | 设计资源 | 设计技巧 | 最新消息 | -| --- | ---------------- | ------------ | ------------ | -| all | design-resources | learn-design | inside-eagle | - - - -## Google - -### Google Fonts - - - -| 最新 | 趋势 | 最受欢迎 | 名字 | 风格数量 | -| :--: | :------: | :--------: | :---: | :---: | -| date | trending | popularity | alpha | style | - -::: warning 注意 - -需要设置 API key,所以只能自建,详情见[部署页面](https://docs.rsshub.app/install/#pei-zhi-bu-fen-rss-mo-kuai-pei-zhi)的配置模块。 - -::: - - - -## Inside Design - -### Recent Stories - - - - -## LogoNews 标志情报局 - -### 首页 - - - -### 文章分类 - - - -如 [简讯 - 标志情报局](https://www.logonews.cn/category/news/newsletter) 的 URL 为 ,可得路由为 [`/logonews/category/news/newsletter`](https://rsshub.app/logonews/category/news/newsletter)。 - - - -### 文章标签 - - - -如 [中国 - 标志情报局](https://www.logonews.cn/tag/china) 的 URL 为 ,可得路由为 [`/logonews/tag/china`](https://rsshub.app/logonews/tag/china)。 - - - -### 作品 - - - -### 作品分类 - - - -如 [LOGO 作品分类:酒店餐饮 - 标志情报局](https://www.logonews.cn/work/categorys/hotel-catering) 的 URL 为 ,可得路由为 [`/logonews/work/categorys/hotel-catering`](https://rsshub.app/logonews/work/categorys/hotel-catering)。 - - - -### 作品标签 - - - -如 [LOGO 标签:旅游 - 标志情报局](https://www.logonews.cn/work/tags/旅游) 的 URL 为 [https://www.logonews.cn/work/tags/ 旅游](https://www.logonews.cn/work/tags/旅游),可得路由为 [`/logonews/work/tags/旅游`](https://rsshub.app/logonews/work/tags/旅游)。 - - - -## Method Studios - -### 菜单 - - - -不支持`news`和`main`。 - -默认为 下的栏目。 - - - -## Monotype - -### Featured Article - - - -## Notefolio - -### Works - - - -| 分类 | 韩文分类名 | 中文分类名 | -| --- | -------- | --------- | -| all | 전체 | 全部 | -| A7 | 공예 | 工艺品 | -| J7 | 그래픽 디자인 | 平面设计 | -| B7 | 디지털 아트 | 数字艺术 | -| C7 | 영상/모션그래픽 | 视频 / 图形动画 | -| D7 | 브랜딩/편집 | 品牌创建 / 编辑 | -| E7 | 산업 디자인 | 工业设计 | -| F7 | UI/UX | UI/UX | -| G7 | 일러스트레이션 | 插画 | -| K7 | 타이포그래피 | 字体 | -| H7 | 파인아트 | 纯艺术 | -| I7 | 포토그래피 | 摄影 | - - - -## UI 中国 - -### 推荐文章 - - - -### 个人作品 - - - -## Unit Image - -### Films - - - -## 爱果果 - -### 最新 H5 - - - -## 优设网 - -### 设计专题 - - - -更多设计专题请参见 [优设专题](https://www.uisdc.com/zt) - - - -### 细节猎人 - - - -更多细节标签请参见 [全部标签](https://www.uisdc.com/alltopics) - - - -### 设计话题 - - - -### 行业新闻 - - - -| 全部新闻 | 活动赛事 | 品牌资讯 | 新品推荐 | -| ---- | --------------- | ---------- | ------------ | -| | events-activity | brand-news | new-products | - - - -### 优设读报 - - - -## 站酷 - -### 发现(+ 推荐预设) - - - -推荐类型 - -| all | home | editor | article | -| ---- | ---- | ------ | ------- | -| 全部推荐 | 首页推荐 | 编辑精选 | 文章推荐 | - - - -### 发现(+ 查询参数) - - - -在 [站酷发现页](https://www.zcool.com.cn/discover) 中选择好所有可选的查询参数后会跳转到对应搜索结果页面。此时地址栏 `https://www.zcool.com.cn/discover/` 后的字段即为查询参数。 - -如:选择 **精选** 分类下的 **运营设计** 子分类后,选择 **编辑精选**,默认 **视频** 取消勾选,默认 **城市** 和 **学校** 留空即全部,就会跳转到链接: - - - -此时其查询参数为 `cate=0&subCate=617&hasVideo=0&city=0&college=0&recommendLevel=2&sort=9`。其对应的路由即 [`/zcool/discover/cate=0&subCate=617&hasVideo=0&city=0&college=0&recommendLevel=2&sort=9`](https://rsshub.app/zcool/discover/cate=0\&subCate=617\&hasVideo=0\&city=0\&college=0\&recommendLevel=2\&sort=9) - - - -### 发现 - - - -查看 **精选** 分类下的全部内容,其他参数选择默认,可直接使用路由 [`/zcool/discover/0`](https://rsshub.app/zcool/discover/0) - -查看 **精选** 分类下的 **运营设计** 子分类全部内容,其他参数选择默认,可直接使用路由 [`/zcool/discover/0/617`](https://rsshub.app/zcool/discover/0/617) - -在 **精选** 分类下的 **运营设计** 子分类全部内容基础上,筛选出有 **视频**,可直接使用路由 [`/zcool/discover/0/617/1`](https://rsshub.app/zcool/discover/0/617/1) - -在 **精选** 分类下的 **运营设计** 子分类全部内容基础上,筛选出有 **视频**,且城市选择 **北京**,可直接使用路由 [`/zcool/discover/0/617/1/北京`](https://rsshub.app/zcool/discover/0/617/1/北京) - -::: tip 提示 - -下方仅提供 **分类及其子分类** 参数的代码。**学校** 参数的代码可以在 [站酷发现页](https://www.zcool.com.cn/discover) 中选择跳转后,从浏览器地址栏中找到。 - -::: - -分类 cate - -| 精选 | 平面 | 插画 | UI | 网页 | 摄影 | 三维 | 影视 | 空间 | 工业 / 产品 | 动漫 | 纯艺术 | 手工艺 | 服装 | 其他 | -| -- | -- | -- | -- | --- | -- | -- | --- | --- | ------- | --- | --- | --- | --- | -- | -| 0 | 8 | 1 | 17 | 607 | 33 | 24 | 610 | 609 | 499 | 608 | 612 | 611 | 613 | 44 | - -子分类 subCate - -精选 0 - -| 运营设计 | 包装 | 动画 / 影视 | 人像摄影 | 商业插画 | 电商 | APP 界面 | 艺术插画 | 家装设计 | 海报 | 文章 | -| ---- | -- | ------- | ---- | ---- | --- | ------ | ---- | ---- | -- | ------ | -| 617 | 9 | 30 | 34 | 2 | 616 | 757 | 292 | 637 | 10 | 809824 | - -平面 8 - -| 包装 | 海报 | 品牌 | IP 形象 | 字体 / 字形 | Logo | 书籍 / 画册 | 宣传物料 | 图案 | 信息图表 | PPT/Keynote | 其他平面 | 文章 | -| -- | -- | -- | ----- | ------- | ---- | ------- | ---- | --- | ---- | ----------- | ---- | --- | -| 9 | 10 | 15 | 779 | 14 | 13 | 12 | 534 | 624 | 625 | 626 | 11 | 809 | - -插画 1 - -| 商业插画 | 概念设定 | 游戏原画 | 绘本 | 儿童插画 | 艺术插画 | 创作习作 | 新锐潮流插画 | 像素画 | 文章 | -| ---- | ---- | ---- | --- | ---- | ---- | ---- | ------ | --- | --- | -| 2 | 5 | 685 | 631 | 684 | 292 | 7 | 3 | 4 | 819 | - -UI 17 - -| APP 界面 | 游戏 UI | 软件界面 | 图标 | 主题 / 皮肤 | 交互 / UE | 动效设计 | 闪屏 / 壁纸 | 其他 UI | 文章 | -| ------ | ----- | ---- | -- | ------- | ------- | ---- | ------- | ----- | --- | -| 757 | 692 | 621 | 20 | 19 | 623 | 797 | 21 | 23 | 822 | - -网页 607 - -| 电商 | 企业官网 | 游戏 / 娱乐 | 运营设计 | 移动端网页 | 门户网站 | 个人网站 | 其他网页 | 文章 | -| --- | ---- | ------- | ---- | ----- | ---- | ---- | ---- | --- | -| 616 | 614 | 693 | 617 | 777 | 615 | 618 | 620 | 823 | - -摄影 33 - -| 人像摄影 | 风光摄影 | 人文 / 纪实摄影 | 美食摄影 | 产品摄影 | 环境 / 建筑摄影 | 时尚 / 艺术摄影 | 修图 / 后期 | 宠物摄影 | 婚礼摄影 | 其他摄影 | 文章 | -| ---- | ---- | --------- | ---- | ---- | --------- | --------- | ------- | ---- | ---- | ---- | --- | -| 34 | 35 | 36 | 825 | 686 | 38 | 800 | 687 | 40 | 808 | 43 | 810 | - -三维 24 - -| 动画 / 影视 | 机械 / 交通 | 人物 / 生物 | 产品 | 场景 | 建筑 / 空间 | 其他三维 | 文章 | -| ------- | ------- | ------- | --- | -- | ------- | ---- | --- | -| 30 | 25 | 27 | 807 | 26 | 29 | 32 | 818 | - -影视 610 - -| 短片 | Motion Graphic | 宣传片 | 影视后期 | 栏目片头 | MV | 设定 / 分镜 | 其他影视 | 文章 | -| --- | -------------- | --- | ---- | ---- | --- | ------- | ---- | --- | -| 645 | 649 | 804 | 646 | 647 | 644 | 650 | 651 | 817 | - -空间 609 - -| 家装设计 | 酒店餐饮设计 | 商业空间设计 | 建筑设计 | 舞台美术 | 展陈设计 | 景观设计 | 其他空间 | 文章 | -| ---- | ------ | ------ | ---- | ---- | ---- | ---- | ---- | --- | -| 637 | 811 | 641 | 636 | 638 | 639 | 640 | 642 | 812 | - -工业 / 产品 499 - -| 生活用品 | 电子产品 | 交通工具 | 工业用品 / 机械 | 人机交互 | 玩具 | 其他工业 / 产品 | 文章 | -| ---- | ---- | ---- | --------- | ---- | --- | --------- | --- | -| 508 | 506 | 509 | 511 | 510 | 689 | 514 | 813 | - -动漫 608 - -| 短篇 / 格漫 | 中 / 长篇漫画 | 网络表情 | 单幅漫画 | 动画片 | 其他动漫 | 文章 | -| ------- | -------- | ---- | ---- | --- | ---- | --- | -| 628 | 629 | 632 | 627 | 633 | 635 | 820 | - -纯艺术 612 - -| 绘画 | 雕塑 | 书法 | 实验艺术 | 文章 | -| --- | --- | --- | ---- | --- | -| 659 | 662 | 668 | 657 | 821 | - -手工艺 611 - -| 工艺品设计 | 手办 / 模玩 | 首饰设计 | 其他手工艺 | 文章 | -| ----- | ------- | ---- | ----- | --- | -| 654 | 656 | 756 | 658 | 816 | - -服装 613 - -| 休闲 / 流行服饰 | 正装 / 礼服 | 传统 / 民族服饰 | 配饰 | 鞋履设计 | 儿童服饰 | 其他服装 | 文章 | -| --------- | ------- | --------- | --- | ---- | ---- | ---- | --- | -| 672 | 671 | 814 | 677 | 676 | 673 | 680 | 815 | - -其他 44 - -| 文案 / 策划 | VR 设计 | 独立游戏 | 其他 | 文章 | -| ------- | ----- | ---- | -- | --- | -| 417 | 798 | 683 | 45 | 824 | - -推荐等级 recommendLevel - -| 全部 | 编辑精选 | 首页推荐 | 全部推荐 | -| -- | ---- | ---- | ---- | -| 0 | 2 | 3 | 1 | - - - -### 作品总榜单 - - - -榜单类型 - -| design | article | -| ------ | ------- | -| 作品榜单 | 文章榜单 | - - - -### 用户作品 - - - -例如: - -站酷的个人主页 `https://baiyong.zcool.com.cn` 对应 rss 路径 `/zcool/user/baiyong` - -站酷的个人主页 `https://www.zcool.com.cn/u/568339` 对应 rss 路径 `/zcool/user/568339` - - diff --git a/docs/en/README.md b/docs/en/README.md deleted file mode 100644 index c7efb347f19167..00000000000000 --- a/docs/en/README.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -sidebarDepth: 0 ---- - -# Introduction - -

- RSSHub -

-

RSSHub

- -> 🍰 Everything is RSSible - -[![telegram](https://img.shields.io/badge/chat-telegram-brightgreen.svg?logo=telegram&style=flat-square)](https://t.me/rsshub) -[![npm publish](https://img.shields.io/github/workflow/status/DIYgod/RSSHub/publish/master?label=npm%20publish&logo=npm&style=flat-square)](https://www.npmjs.com/package/rsshub) -[![docker publish](https://img.shields.io/github/workflow/status/DIYgod/RSSHub/%5Bdocker%5D%20CI%20for%20releases/master?label=docker%20publish&logo=docker&style=flat-square)](https://hub.docker.com/r/diygod/rsshub) -[![test](https://img.shields.io/github/workflow/status/DIYgod/RSSHub/test/master?label=test&logo=github&style=flat-square)](https://github.com/DIYgod/RSSHub/actions/workflows/test.yml?query=event%3Apush+branch%3Amaster) -[![Test coverage](https://img.shields.io/codecov/c/github/DIYgod/RSSHub.svg?style=flat-square&logo=codecov)](https://app.codecov.io/gh/DIYgod/RSSHub/branch/master) - -RSSHub is an open source, easy to use, and extensible RSS feed aggregator, it's capable of generating RSS feeds from pretty much everything. - -RSSHub delivers millions of contents aggregated from all kinds of sources, our vibrant open source community is ensuring the deliver of RSSHub's new routes, new features and bug fixes. - -RSSHub can be used with browser extension [RSSHub Radar](https://github.com/DIYgod/RSSHub-Radar) and mobile auxiliary app [RSSBud](https://github.com/Cay-Zhang/RSSBud) (iOS) and [RSSAid](https://github.com/LeetaoGoooo/RSSAid) (Android) - -## Special Thanks - -### Special Sponsors - -RSS3 - -### Sponsors - -[Sayori Studio](https://t.me/SayoriStudio) . [Sion Kazama](https://blog.sion.moe) . [琚致远](https://wineso.me/) . [Rolly RSS 阅读器](https://www.coolapk.com/apk/239500) . [mokeyjay](https://www.mokeyjay.com/) . [萌开源联盟](https://www.moeunion.com) . [hooke007](https://github.com/hooke007/MPV_lazy) . [feeds.pub](https://feeds.pub) . [KINGX@安全引擎](http://cve.today/) - -[![](https://opencollective.com/static/images/become_sponsor.svg)](/en/support/) - -### Contributors - -[![](https://opencollective.com/RSSHub/contributors.svg?width=740)](https://github.com/DIYgod/RSSHub/graphs/contributors) - -Logo designer [sheldonrrr](https://dribbble.com/sheldonrrr) - -### Backers - - - -## Related Projects - -- [RSSHub Radar](https://github.com/DIYgod/RSSHub-Radar) | 一个可以帮助你快速发现和订阅当前网站 RSS 和 RSSHub 的浏览器扩展 -- [RSSBud](https://github.com/Cay-Zhang/RSSBud) ([TestFlight 公测](https://testflight.apple.com/join/rjCVzzHP)) | iOS 平台的 RSSHub Radar,专为移动生态优化 -- [RSSAid](https://github.com/LeetaoGoooo/RSSAid) | 基于 Flutter 构建的 Android 平台的 RSSHub Radar -- [DocSearch](https://github.com/Fatpandac/DocSearch) | Link RSSHub DocSearch into Raycast diff --git a/docs/en/anime.md b/docs/en/anime.md deleted file mode 100644 index b319d66697dee8..00000000000000 --- a/docs/en/anime.md +++ /dev/null @@ -1,180 +0,0 @@ ---- -pageClass: routes ---- - -# ACG - -## Bangumi Moe - -### Latest - - - -### Tags - - - -For more tags, please go to [Search torrent](https://bangumi.moe/search/index) - - - -## Hanime.tv - -### Recently updated - - - -## iwara - -### User - - - -| type | video | image | -| :--: | :---: | :---: | -| | video | image | - - - -### User Subscriptions - - - -::: warning - -This route requires Cookie, therefore it's only available when self-hosting, refer to the [Deploy Guide](/en/install/#route-specific-configurations) for route-specific configurations. - -::: - - - -## Kemono - -### Posts - - - -Sources - -| Posts | Patreon | Pixiv Fanbox | Gumroad | SubscribeStar | DLsite | Discord | Fantia | -| ----- | ------- | ------------ | ------- | ------------- | ------ | ------- | ------ | -| posts | patreon | fanbox | gumroad | subscribestar | dlsite | discord | fantia | - -::: tip Tip - -When `posts` is selected as the value of the parameter **source**, the parameter **id** does not take effect. - -::: - - - -## lovelive-anime - -### Love Live! Official Website Latest NEWS - - - -### Love Live Official Website Categories Topics - - - -| Sub-project name (not full name) | Lovelive! | Lovelive! Sunshine!! | Lovelive! Nijigasaki High School Idol Club | Lovelive! Superstar!! | -| -------------------------------- | ----------- | -------------------- | ------------------------------------------ | --------------------- | -| `abbr`parameter | otonokizaka | uranohoshi | nijigasaki | yuigaoka | - - -| category name | 全てのニュース | 音楽商品 | アニメ映像商品 | キャスト映像商品 | 劇場 | アニメ放送/配信 | キャスト配信/ラジオ | ライブ/イベント | ブック | グッズ | ゲーム | メディア | ご当地情報 | その他 | キャンペーン | -| -------------- | --------------- | -------- | -------------- | ---------------- | ------- | --------------- | ------------------- | --------------- | ------ | ------ | ------ | -------- | ---------- | ------ | ------------ | -| `category`parameter | *No parameter* | music | anime_movie | cast_movie | theater | onair | radio | event | books | goods | game | media | local | other | campaign | - - - -## THBWiki - -### Calendar - - - -## Touhougarakuta - -### Articles - - - -Languages: - -| Chinese | Japanese | Korean | -| ---- | ---- | ---- | -| cn | ja | ko | - -Article types: - -| Index | Series | Interviews | Novels | Comics | News | -| -------- | ------ | ---------- | ------ | ------ | ---- | -| index | series | interviews | novels | comics | news | - -| Music review | Game review | Book review | Where are you | -| ------------ | ----------- | ------------ | ------------- | -| music_review | game_review | book_review | where_are_you | - -**Note:** The index type includes all types of articles. Think twice before using it. - - - -## Vol.moe - -### vol - - - -| Comics are serialized | Comics is finshed | -| --------------------- | ----------------- | -| serial | finish | - - - -## Webtoons - -### Comic updates - - - -For example: , `lang=zh-hant`,`category=drama`,`name=gongzhucheyeweimian`,`id=894`. - -### [Naver](https://comic.naver.com) - - - -## 俺の3Dエロ動画(oreno3d) - -::: tip Tip - -You can use some RSS parsing libraries (like `feedpraser` in `Python`) to receive the video update messages and download them automatically - -::: - -### Keyword Search - - - -| favorites | hot | latest | popularity | -| --------- | ---- | ------ | ---------- | -| favorites | hot | latest | popularity | - - - -### Character Search - - - -### Author Search - - - -### Tags Search - - - -### Origins Search - - diff --git a/docs/en/api.md b/docs/en/api.md deleted file mode 100644 index ec91e1fe0f637d..00000000000000 --- a/docs/en/api.md +++ /dev/null @@ -1,42 +0,0 @@ -# API - -::: warning Warning -The API is under active development and is subject to change. All suggestions are welcome! -::: - -RSSHub provides the following APIs: - -## List of Public Routes - -::: tip Tip -This API **will not** return any routes under `lib/protected_router.js`. -::: - -Eg: - -Route: `/api/routes/:name?` - -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: - -```js -{ - "status": "success", - "data": { - "github": { - "routes": [ - "/github/trending/:since/:language?", - "/github/issue/:user/:repo", - "/github/user/followers/:user", - "/github/stars/:user/:repo" - ] - } - }, - "message": "request returned 4 routes" -} -``` - -If no matching results were found, the server returns only a HTTP status code `204 No Content`. diff --git a/docs/en/bbs.md b/docs/en/bbs.md deleted file mode 100644 index f2deab5d1d3ac7..00000000000000 --- a/docs/en/bbs.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -pageClass: routes ---- - -# BBS - -## Discuz - -### General Subforum - Auto detection - - - -### General Subforum - Manual version - - - -| Discuz X Series | Discuz 7.x Series | -| --------------- | ----------------- | -| x | 7 | - - - -### General Subforum - Support cookie - - - -| Discuz X Series | Discuz 7.x Series | -| --------------- | ----------------- | -| x | 7 | - - - -## LowEndTalk - -### Discussion - - - -## Mobilism - -### Forums - - - -| Android | iPhone | iPad | -| ------- | ------ | ---- | -| android | iphone | ipad | - -| Apps | games | -| ---- | ----- | -| apps | games | - - - -### Portal - - - -| Android Apps | Android Games | ebook | iPad Apps | iPad Games | iPhone Apps | iPhone Games | -| ------------ | ------------- | ----- | --------- | ---------- | ----------- | ------------ | -| aapp | agame | ebook | ipapp | ipgame | iapp | igame | - - - -## SCBOY forum - -### Thread - - - -If the url of the thread is then tid would be `1789863`. - -When accessing Joeyray's Bar, `SCBOY_BBS_TOKEN` needs to be filled in `environment`. See for details. `SCBOY_BBS_TOKEN` is included in cookies with `bbs_token`. - - - -## ZodGame - -### forum - - diff --git a/docs/en/blog.md b/docs/en/blog.md deleted file mode 100644 index 063b8b83505d3d..00000000000000 --- a/docs/en/blog.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -pageClass: routes ---- - -# Blog - -## archdaily - -### Home - - - -## CSDN - -### User Feed - - - -## Google Sites - -### Articles - - - -### Recent Changes - - - -## Hexo Blog - -### Blog using Next theme - - - -### Blog using Yilia theme - - - -### Blog using Fluid theme - - - -## Love the Problem - -### Ash Maurya's blog - - - -## Miris Whispers - -### Blog - - - -## Paul Graham - -### Essays - - - -## Phrack Magazine - -### Article - - - -## Polkadot - -### Blog - - - -## PolkaWorld - -### Newest Articles - - - -::: tip - -Limit the number of entries to be retrieved by adding `?limit=x` to the end of the route, default value is `10`. - -::: - - - -## Stratechery by Ben Thompson - -### Blog - - - -## WordPress - -### Blog - - - -## yuzu emulator - -### Entry - - - diff --git a/docs/en/design.md b/docs/en/design.md deleted file mode 100644 index 5893dffe3ac4fc..00000000000000 --- a/docs/en/design.md +++ /dev/null @@ -1,131 +0,0 @@ ---- -pageClass: routes ---- - -# Design - -## Axis Studios - -### Work type - - - -Work type URL in articles. Such as: 'https://axisstudiosgroup.com/work/full-service-cg-production' the tag will be `full-service-cg-production`. - -Some tags are rarely used: `Script`, `direction`, `production`, `design-concept` etc。 - - - -## Behance - -### User Works - - - -Behance user's profile URL, like the username will be `mishapetrick`。 - - - -## Blow Studio - -### Home - - - -## Blur Studio - -### Works - - - -## Digic Picture - -### Works & News - - - -## Dribbble - -### Popular - - - -### User (or team) - - - -### Keyword - - - -## Eagle - -### Blog - - - -## Google - -### Google Fonts - - - -| Newest | Trending | Most popular | Name | Number of styles | -| :----: | :------: | :----------: | :--: | :--------------: | -| date | trending | popularity | alpha | style | - -::: warning - -This route requires API key, therefore it's only available when self-hosting, refer to the [Deploy Guide](https://docs.rsshub.app/en/install/#configuration-route-specific-configurations) for route-specific configurations. - -::: - - - -## Inside Design - -### Recent Stories - - - - -## Method Studios - -### Menus - - - -Not support `main`, `news`. - -Default is under 'https://www.methodstudios.com/en/features'. - - - -## Notefolio - -### Works - - - -| Category | Name in Korean | Name in English | -| -------- | -------------- | --------------- | -| all | 전체 | All | -| A7 | 공예 | Crafts | -| J7 | 그래픽 디자인 | Graphic Design | -| B7 | 디지털 아트 | Digital Art | -| C7 | 영상/모션그래픽 | Video / Motion Graphics | -| D7 | 브랜딩/편집 | Branding / Editing | -| E7 | 산업 디자인 | Industrial Design | -| F7 | UI/UX | UI/UX | -| G7 | 일러스트레이션 | Illustration | -| K7 | 타이포그래피 | Typography | -| H7 | 파인아트 | Fine Art | -| I7 | 포토그래피 | Photography | - - - -## Unit Image - -### Films - - diff --git a/docs/en/faq.md b/docs/en/faq.md deleted file mode 100644 index 5766435ee3f84f..00000000000000 --- a/docs/en/faq.md +++ /dev/null @@ -1,25 +0,0 @@ -# FAQs - -**Q: How does RSSHub work?** - -**A:** When a request is received, RSSHub fetches the corresponding data from the original site, the resulting contents will be outputted in RSS format. Caching is implemented to avoid requesting original sites for content. And of course, we throw in a little magic 🎩. - -**Q: Can I use the demo instance?** - -**A:** [rsshub.app](https://rsshub.app) is the demo instance provided, running the latest build of RSSHub from master branch, the cache is set 120 minutes and it's free to use. However, if you see an badge for route, this means popular websites such as Facebook etc. may pose a request quota on individual IP address, which means it can get unreliable from time to time for the demo instance. You are encouraged to [host your own RSSHub instance](/en/install/) to get a better usability. - -**Q: Why are images/videos not loading in some RSSHub routes?** - -**A:** RSSHub fetches and respects the original image/video URLs from original sites, in which some are behind anti-hotlink filters. `referrerpolicy="no-referrer"` attribute is added to all images to solve the issues caused by cross-domain requests. Third party RSS service providers such as Feedly and Inoreader, strip this attribute off, resulting in cross-domain requests being blocked. Meanwhile, the attribute is not available for videos yet, resulting in most RSS readers unable to pass the anti-hotlink check. Here are some workarounds: - -1. Migrate to RSS readers that do not send Referer,such as [Inoreader for Web](https://www.inoreader.com/) with a [user script disabling Referer](https://greasyfork.org/en/scripts/376884), [RSS to Telegram Bot](https://github.com/Rongronggg9/RSS-to-Telegram-Bot), etc. If your RSS reader can bypass the anti-hotlink check successfully and play embedded videos, it's an RSS reader that do not send Referer. Please consider adding it to the documentation to help more people. -2. Set up a reverse proxy, refer to [Parameters->Multimedia processing](/en/parameter.html#multimedia-processing) for more details. -3. Navigate back to the original site. - -**Q: The website I want is not supported QAQ** - -**A:** If you are a JavaScript developer, please follow [this guide](/en/joinus/quick-start.html) for submitting a pull request, otherwise, follow the issue template to [submit a new issue](https://github.com/DIYgod/RSSHub/issues/new?template=rss_request_en.md), and patiently wait for Santa Claus. For priority responses, consider [sponsoring us](/en/support). - -**Q: Where do I get the changelog for RSSHub?** - -**A:** Subscribe our RSS here: [RSSHub added a new route](/en/program-update.html#rsshub). diff --git a/docs/en/finance.md b/docs/en/finance.md deleted file mode 100644 index 8ec85407c0b2de..00000000000000 --- a/docs/en/finance.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -pageClass: routes ---- - -# Finance - -## CFD - -### Indices Dividend Adjustment (GBP) - - - -## finviz - -### News - - - -| News | Blog | -| ---- | ---- | -| news | blog | - - - -### US Stock News - - - -## FX Markets - -### Channel - - - -| Trading | Infrastructure | Tech and Data | Regulation | -| ------- | -------------- | ------------- | ---------- | -| trading | infrastructure | tech-and-data | regulation | - - - -## Seeking Alpha - -### Summary - - - -| Analysis | News | Transcripts | Press Releases | Related Analysis | -| ------- | ------- | -------- | ---- | ------ | -| analysis | news | transcripts | press-releases | related-analysis | - - - -## TokenInsight - -::: tip Tips - -TokenInsight also provides official RSS, you can take a look at . - -::: - -### Blogs - - - -### Latest - - - -### Research - - - -Language: - -| Chinese | English | -| ------- | ------- | -| zh | en | - - - -## Unusual Whales - -### News Flow - - - -## World Economic Forum - -### Report - - - -Languages - -| English | Español | Français | 中文 | 日本語 | -| ------- | ------- | -------- | ---- | ------ | -| en | es | fr | cn | jp | - -See filters in [Report](https://www.weforum.org/reports) for Year and Platform these two parameters. - - diff --git a/docs/en/forecast.md b/docs/en/forecast.md deleted file mode 100644 index 30de37fa7819c1..00000000000000 --- a/docs/en/forecast.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -pageClass: routes ---- - -# Forecast - -## Outage.Report - -### Report - - - -Please skip the local service area code for `name`, for example `https://outage.report/us/verizon-wireless` to `verizon-wireless`. - - diff --git a/docs/en/game.md b/docs/en/game.md deleted file mode 100755 index 66a24c2641a996..00000000000000 --- a/docs/en/game.md +++ /dev/null @@ -1,339 +0,0 @@ ---- -pageClass: routes ---- - -# Gaming - -## Blizzard - -### News - - - -Categories - -| Category | Slug | -| ---------------------- | ------------------- | -| All News | | -| Diablo III | diablo3 | -| Diablo IV | diablo4 | -| Diablo: Immortal | diablo-immortal | -| Hearthstone | hearthstone | -| Heroes of the Storm | heroes-of-the-storm | -| Overwatch | overwatch | -| StarCraft: Remastered | starcraft | -| StarCraft II | starcraft2 | -| World of Warcraft | world-of-warcraft | -| Warcraft III: Reforged | warcraft3 | -| BlizzCon | blizzcon | -| Inside Blizzard | blizzard | - -Language codes - -| Language | Code | -| -------------- | ----- | -| Deutsch | de-de | -| English (US) | en-us | -| English (EU) | en-gb | -| Español (EU) | es-es | -| Español (AL) | es-mx | -| Français | fr-fr | -| Italiano | it-it | -| Português (AL) | pt-br | -| Polski | pl-pl | -| Русский | ru-ru | -| 한국어 | ko-kr | -| ภาษาไทย | th-th | -| 日本語 | ja-jp | -| 繁體中文 | zh-tw | -| 简体中文 | zh-cn | - - - -## dekudeals - -### Category - - - -## Epic Games Store - -### Free games - - - -## FINAL FANTASY XIV - -### FINAL FANTASY XIV (The Lodestone) - - - -Region - -| North Ameria | Europe | France | Germany | Japan | -| ------------ | ------ | ------ | ------- | ----- | -| na | eu | fr | de | jp | - -Category - -| all | topics | notices | maintenance | updates | status | developers | -| --- | ------ | ------- | ----------- | ------- | -------- | ---------- | - - - -## Gamer Secret - -### Latest News - - - -### Category - - - -| Latest News | PC | Playstation | Nintendo | Xbox | Moblie | -| ----------- | -- | ----------- | -------- | ---- | ------ | -| latest-news | pc | playstation | nintendo | xbox | moblie | - -Or - -| GENERAL | GENERAL EN | MOBILE | MOBILE EN | -| ---------------- | ------------------ | --------------- | ----------------- | -| category/general | category/generalen | category/mobile | category/mobileen | - -| NINTENDO | NINTENDO EN | PC | PC EN | -| ----------------- | ------------------- | ----------- | ------------- | -| category/nintendo | category/nintendoen | category/pc | category/pcen | - -| PLAYSTATION | PLAYSTATION EN | REVIEWS | -| -------------------- | ---------------------- | ---------------- | -| category/playstation | category/playstationen | category/reviews | - -| XBOX | XBOX EN | -| ------------- | --------------- | -| category/xbox | category/xboxen | - - - -## itch.io - -### Browse - - - -`params` is the field after `itch.io` in the URL of the corresponding page, e.g. the URL of [Top Rated Games tagged Singleplayer](https://itch.io/games/top-rated/tag-singleplayer) is , where the field after `itch.io` is `/games/top-rated/tag-singleplayer`. - -So the route is [`/itch/games/top-rated/tag-singleplayer`](https://rsshub.app/itch/games/top-rated/tag-singleplayer). - -::: tip tips - -You can browse all the tags at [here](https://itch.io/tags). - -::: - - - -### Developer Logs - - - -`User id` is the field before `.itch.io` in the URL of the corresponding page, e.g. the URL of [The Baby In Yellow Devlog](https://teamterrible.itch.io/the-baby-in-yellow/devlog) is , where the field before `.itch.io` is `teamterrible`. - -`Item id` is the field between `itch.io` and `/devlog` in the URL of the corresponding page, e.g. the URL for [The Baby In Yellow Devlog](https://teamterrible.itch.io/the-baby-in-yellow/devlog) is , where the field between `itch.io` and `/devlog` is `the-baby-in-yellow`. - -So the route is [`/itch/devlogs/teamterrible/the-baby-in-yellow`](https://rsshub.app/itch/devlogs/teamterrible/the-baby-in-yellow). - - - -### Posts - - - -## Konami - -### PES Mobile Announcement - - - -## Metacritic - -### Game Releases - - - -Platforms supported: - -| PS 4 | Xbox One | Switch | PC | Wii U | 3DS | PS Vita | iOS | -| ---- | -------- | ------ | --- | ----- | --- | ------- | --- | -| ps4 | xboxone | switch | pc | wii-u | 3ds | vita | ios | - -Release types, default to `new`: - -| New | Coming Soon | All | -| --- | ----------- | --- | -| new | coming | all | - -Sorting types, default to `date`: - -| Date | Metacritic Score | User Score | -| ---- | ---------------- | ---------- | -| date | metascore | userscore | - - - -## Minecraft - -### Java Game Update - - - -### CurseForge Mod Update - - - -### Feed The Beast Modpack Updates - - -| param | description | -| ------| ------------ | -| modpackEntry | The entry name of modpack, can be found in modpack\'s page link, for `https://www.feed-the-beast.com/modpack/ftb_presents_direwolf20_1_16`, use `ftb_presents_direwolf20_1_16`. | - - -## Nintendo - -### eShop New Game Releases - - - -### Nintendo Direct - - - -### News(Hong Kong only) - - - -### Switch System Update(Japan) - - - -## PlayStation Store - -### Game List(Hong Kong) - - - -Compatible with lists with an URL like . For instance [PSN Free to Play](https://store.playstation.com/zh-hans-hk/grid/STORE-MSF86012-PLUS_FTT_CONTENT), the gridName is STORE-MSF86012-PLUS_FTT_CONTENT - - - -### Game Product Price - - - -Tested some countries, it should be work for most. - -Compatible with Product with an URL like . For instance ['Cyberpunk 2077'](https://store.playstation.com/en-us/product/HP4497-CUSA16570_00-ASIAFULLGAME0000) the region is `en-us`, the gridName is `HP4497-CUSA16570_00-ASIAFULLGAME0000` - - - -### PlayStation Network user trophy - - - -### PlayStation 4 System Update - - - -## ProjectSekai | プロセカ - -### News - - - -## Steam - -### Steam search - - - -Get serach parameters from the URL. - -For instance, in `https://store.steampowered.com/search/?specials=1&term=atelier`, the parameters are `specials=1&term=atelier`. - - - -### Steam news - -::: tip - -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/) - -::: - -## SteamGifts - -### Discussions - - - -## TapTap International - -::: warning Warning - -Due to the regional restrictions, an RSSHub deployment in China Mainland may not work on accessing the TapTap International Website. - -::: - -### Game Topics - -::: tip Tips - -Unlike TapTap China Mainland Website, the International Website has no BBS. - -::: - -### Game's Changelog - - - -#### Language Code - -| English (US) | 繁體中文 | 한국어 | 日本語 | -| ----- | ----- | ----- | ----- | -| en_US | zh_TW | ko_KR | ja_JP | - - - -### Ratings & Reviews - - - -#### Sort Method - -| Most Relevant | Most Recent | -| -------------- | ---- | -| default | new | - -#### Language Code - -| English (US) | 繁體中文 | 한국어 | 日本語 | -| ----- | ----- | ----- | ----- | -| en_US | zh_TW | ko_KR | ja_JP | - - - -## War Thunder - -### News - - - -News data from https://warthunder.com/en/news/ -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/government.md b/docs/en/government.md deleted file mode 100644 index 5b811071608b07..00000000000000 --- a/docs/en/government.md +++ /dev/null @@ -1,194 +0,0 @@ ---- -pageClass: routes ---- - -# Government - -## Australia Department of Home Affairs - -### Immigration and Citizenship - News - - - -## Central Intelligence Agency - -### Annual FOIA Reports - - - -## Constitutional Court of Baden-Württemberg (Germany) - -### Press releases - - - -## Hong Kong Centre for Health Protection - -### Category - - - -Category - -| Important Topics | Press Releases | Response Level | Periodicals & Publications | Health Notice | -| ---------------- | ---------------- | -------------- | -------------------------- | ------------- | -| important_ft | press_data_index | ResponseLevel | publication | HealthAlert | - -Language - -| English | 中文简体 | 中文繁體 | -| ------- | -------- | -------- | -| en | zh_cn | zh_tw | - - - -## Hong Kong Department of Health - -### Press Release - - - -Language - -| English | 中文简体 | 中文繁體 | -| ------- | -------- | -------- | -| english | chs | tc_chi | - - - -## Hong Kong Independent Commission Against Corruption - -### Press Releases - - - -## Macau Independent Commission Against Corruption - -### Latest News - - -Category - -| All | Detected Cases | Investigation Reports or Recommendations | Annual Reports | CCAC's Updates | -| ---- | -------------- | ----------------------------------------- | -------------- | -------------- | -| all | case | Persuasion | AnnualReport | PCANews | - - - -## Ministry of Foreign Affairs of Japan - -### Press conference - - - -## Supreme Court of the United States - -### Arguments Audios - - - -## The United States Trade Representative - -### Press Releases - - - -::: tip Tip - -Fill in the English expression for the month in the Month field, eg `December` for the 12th Month。 - -::: - - - -## The White House - -### Briefing Room - - - -| All | Blog | Legislation | Presidential Actions | Press Briefings | Speeches and Remarks | Statements and Releases | -| - | - | - | - | - | - | - | -| | blog | legislation | presidential-actions | press-briefings | speeches-remarks | statements-releases | - - - -### Office of Science and Technology Policy - - - -## U.S. Department of the Treasury - -### Press Releases - - - -Category - -| Press Releases | Statements & Remarks | Readouts | Testimonies | -| -------------- | -------------------- | -------- | ----------- | -| all | statements-remarks | readouts | testimonies | - - - -## U.S. Food and Drug Administration - -### CDRHNew - - - -## United Nations - -### Security Council Vetoed a Resolution - - - -## World Health Organization | WHO - -### News - - - -Language - -| English | العربية | 中文 | Français | Русский | Español | Português | -| ------- | ------- | ---- | -------- | ------- | ------- | --------- | -| en | ar | zh | fr | ru | es | pt | - - - -### Newsroom - - - -Category - -| Feature stories | Commentaries | -| --------------- | ------------ | -| feature-stories | commentaries | - -Language - -| English | العربية | 中文 | Français | Русский | Español | Português | -| ------- | ------- | ---- | -------- | ------- | ------- | --------- | -| en | ar | zh | fr | ru | es | pt | - - - -### Speeches - - - -Language - -| English | العربية | 中文 | Français | Русский | Español | Português | -| ------- | ------- | ---- | -------- | ------- | ------- | --------- | -| en | ar | zh | fr | ru | es | pt | - - - -## World Trade Organization - -### Dispute settlement news - - diff --git a/docs/en/install/README.md b/docs/en/install/README.md deleted file mode 100644 index cccf377ce471ca..00000000000000 --- a/docs/en/install/README.md +++ /dev/null @@ -1,725 +0,0 @@ ---- -sidebar: auto ---- - -# Deployment - -RSSHub provides a painless deployment process if you are equipped with basic programming knowledge, you may open an [issue](https://github.com/DIYgod/RSSHub/issues/new/choose) if you believe you have encountered a problem not listed [here](https://github.com/DIYgod/RSSHub/issues), the community will try to sort it out asap. - -The deployment may involve the followings: - -1. Command line interface -2. [Git](https://git-scm.com/) -3. [Node.js](https://nodejs.org/) -4. [npm](https://www.npmjs.com/get-npm) or [yarn](https://yarnpkg.com/zh-Hans/docs/install) - -Deploy for public access may require: - -1. [Nginx](https://www.nginx.com/resources/wiki/start/topics/tutorials/install/) -2. [Docker](https://www.docker.com/get-started) or [docker-compose](https://docs.docker.com/compose/install/) -3. [Redis](https://redis.io/download) -4. [Heroku](https://devcenter.heroku.com/articles/getting-started-with-nodejs) -5. [Google App Engine](https://cloud.google.com/appengine/) - -## Docker Image - -We recommend using the latest version `diygod/rsshub` (i.e. `diygod/rsshub:latest`) of the docker image. - -When the latest version is unstable, you can use the image with a date tag for temporary use. For example: - -```bash -$ docker pull diygod/rsshub:2021-06-18 -``` - -You can back to the latest version when the code has been fixed and rebuild the image. - -To enable puppeteer, `diygod/rsshub:chromium-bundled` is a good choice. If date specified, it will become: `diygod/rsshub:chromium-bundled-2021-06-18`. - -Another approach to enable puppeteer is deploying with Docker Compose. However, it consumes more disk space and memory. By modifying `docker-compose.yml`, you can use `diygod/rsshub:chromium-bundled` instead to reduce the disk space and memory consumption. - -## Docker Compose Deployment - -### Install - -Download [docker-compose.yml](https://github.com/DIYgod/RSSHub/blob/master/docker-compose.yml) - -```bash -$ wget https://raw.githubusercontent.com/DIYgod/RSSHub/master/docker-compose.yml -``` - -Check if any configuration needs to be changed - -```bash -$ vi docker-compose.yml # or your favorite editor -``` - -Create a docker volume to persist Redis caches - -```bash -$ docker volume create redis-data -``` - -Launch - -```bash -$ docker-compose up -d -``` - -### Update - -Remove old containers - -```bash -$ docker-compose down -``` - -Repull the latest image if you have downloaded the image before. It is helpful to resolve some issues. - -```bash -$ docker pull diygod/rsshub -``` - -Then repeat the installation steps - -### Configuration - -Edit `environment` in [docker-compose.yml](https://github.com/DIYgod/RSSHub/blob/master/docker-compose.yml) - -## Docker Deployment - -::: tip Tip - -To enable puppeteer, replace `diygod/rsshub` with `diygod/rsshub:chromium-bundled` in **EACH** command. - -::: - -### Install - -Execute the following command to pull RSSHub's docker image. - -```bash -$ docker pull diygod/rsshub -``` - -Start a RSSHub container - -```bash -$ docker run -d --name rsshub -p 1200:1200 diygod/rsshub -``` - -Visit [http://127.0.0.1:1200/](http://127.0.0.1:1200/), and enjoy it! ✅ - -Execute the following command to stop `RSSHub`. - -```bash -$ docker stop rsshub -``` - -### Update - -Remove the old container - -```bash -$ docker stop rsshub -$ docker rm rsshub -``` - -Then repeat the installation steps - -### Configuration - -The simplest way to configure RSSHub container is via system environment variables. - -For example, adding `-e CACHE_EXPIRE=3600` will set the cache time to 1 hour. - -```bash -$ docker run -d --name rsshub -p 1200:1200 -e CACHE_EXPIRE=3600 -e GITHUB_ACCESS_TOKEN=example diygod/rsshub -``` - -This deployment method does not include puppeteer (unless using `diygod/rsshub:chromium-bundled` instead) and Redis dependencies. Use the Docker Compose deployment method or deploy external dependencies yourself if you need it. - -To configure more options please refer to [Configuration](#configuration). - -## Ansible Deployment - -This Ansible playbook includes RSSHub, Redis, browserless (uses Docker) and Caddy 2 - -Currently only support Ubuntu 20.04 - -Requires sudo privilege and virtualization capability (Docker will be automatically installed) - -### Install - -```bash -sudo apt update -sudo apt install ansible -git clone https://github.com/DIYgod/RSSHub.git ~/RSSHub -cd ~/RSSHub/scripts/ansible -sudo ansible-playbook rsshub.yaml -# When prompt to enter a domain name, enter the domain name that this machine/VM will use -# For example, if your users use https://rsshub.example.com to access your RSSHub instance, enter rsshub.example.com (remove the https://) -``` - -### Update - -```bash -cd ~/RSSHub/scripts/ansible -sudo ansible-playbook rsshub.yaml -# When prompt to enter a domain name, enter the domain name that this machine/VM will use -# For example, if your users use https://rsshub.example.com to access your RSSHub instance, enter rsshub.example.com (remove the https://) -``` - -## Manual Deployment - -The most direct way to deploy `RSSHub`, you can follow the steps below to deploy`RSSHub` on your computer, server or anywhere. - -### Install - -Execute the following commands to download the source code - -```bash -$ git clone https://github.com/DIYgod/RSSHub.git -$ cd RSSHub -``` - -Execute the following commands to install dependencies (Do not add the `--production` parameter for development). - -Using `yarnv1` - -```bash -$ yarn --production -``` - -or using `npm` - -```bash -$ npm ci --production -``` - -### Launch - -Under `RSSHub`'s root directory, execute the following commands to launch - -```bash -$ yarn start -``` - -Or -```bash -$ npm start -``` - -Or use [PM2](https://pm2.io/docs/plus/quick-start/) - -```bash -$ pm2 start lib/index.js --name rsshub -``` - -Visit [http://127.0.0.1:1200/](http://127.0.0.1:1200/), and enjoy it! ✅ - -Refer to our [Guide](https://docs.rsshub.app/en/) for usage. Replace `https://rsshub.app/` with `http://localhost:1200` in any route example to see the effect. - -### Configuration - -::: tip Tip - -On arm/arm64, this deployment method does not include puppeteer dependencies. To enable puppeteer, install Chromium from your distribution repositories first, then set `CHROMIUM_EXECUTABLE_PATH` to its executable path. - -Debian: -```bash -$ apt install chroium -$ echo >> .env -$ echo 'CHROMIUM_EXECUTABLE_PATH=chromium' >> .env -``` - -Ubuntu/Raspbian: -```bash -$ apt install chromium-browser -$ echo >> .env -$ echo 'CHROMIUM_EXECUTABLE_PATH=chromium-browser' >> .env -``` - -::: - -RSSHub can be configured by setting environment variables. - -Create a `.env` file in the root directory of your project. Add environment-specific variables on new lines in the form of `NAME=VALUE`. For example: - -``` -CACHE_TYPE=redis -CACHE_EXPIRE=600 -``` - -Please notice that it will not override already existed environment variables, more rules please refer to [dotenv](https://github.com/motdotla/dotenv) - -This deployment method does not include Redis dependencies. Use the Docker Compose deployment method or deploy external dependencies yourself if you need it. - -To configure more options please refer to [Configuration](#configuration). - -### Update - -Under `RSSHub`'s directory, execute the following commands to pull the latest source code for `RSSHub` - -```bash -$ git pull -``` - -Then repeat the installation steps. - -### A tip for Nix users - -To install nodejs, yarn and jieba (to build documentation) you can use the following `nix-shell` configuration script. - -```nix -let - pkgs = import {}; - node = pkgs.nodejs-12_x; -in pkgs.stdenv.mkDerivation { - name = "nodejs-yarn-jieba"; - buildInputs = [node pkgs.yarn pkgs.pythonPackages.jieba]; -} -``` - -## Deploy to Heroku - -### Notice - -::: warning Update - -Heroku [no longer](https://blog.heroku.com/next-chapter) offers free product plans. - -::: - -~~Heroku accounts with unverified payment methods have only 550 hours of credit per month (about 23 days), and up to 1,000 hours per month with verified payment methods.~~ - -### Instant deploy (without automatic update) - -[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https%3A%2F%2Fgithub.com%2FDIYgod%2FRSSHub) - -### Automatic deploy upon update - -1. [Fork RSSHub](https://github.com/login?return_to=%2FDIYgod%2FRSSHub) to your GitHub account. -2. Deploy your fork to Heroku: `https://heroku.com/deploy?template=URL`, where `URL` is your fork address (_e.g._ `https://github.com/USERNAME/RSSHub`). -3. Configure `automatic deploy` in Heroku app to follow the changes to your fork. -4. Install [Pull](https://github.com/apps/pull) app to keep your fork synchronized with RSSHub. - -## Deploy to Vercel(Zeit Now) - -[![Deploy to Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/DIYgod/RSSHub) - -## Deploy to Google App Engine(GAE) - -### Before You Begin - -Follow the [official guide](https://cloud.google.com/appengine/docs/flexible/nodejs/quickstart) for completing your GCP account settings, creating a new Node project, adding billing information (required), installing git and initializing gcloud([link](https://cloud.google.com/sdk/gcloud/)). Node.js is not required if you don't plan to debug RSSHub locally. - -Please note, GAE free tier doesn't support Flexible Environment, please check the pricing plan prior to deployment. - -Node.js standard environment is still under beta, unknown or unexpected errors might be encountered during the deployment. - -Execute `git clone https://github.com/DIYgod/RSSHub.git` to pull the latest code - -### app.yaml Settings - -#### Deploy to Flexible Environment - -Under RSSHub's root directory, create a file `app.yaml` with the following content: - -```yaml -# [START app_yaml] -runtime: custom -env: flex - -# This sample incurs costs to run on the App Engine flexible environment. -# The settings below are to reduce costs during testing and are not appropriate -# for production use. For more information, see: -# https://cloud.google.com/appengine/docs/flexible/nodejs/configuring-your-app-with-app-yaml -manual_scaling: - instances: 1 -# app engine resources, adjust to suit your needs, the required disk space is 10 GB -resources: - cpu: 1 - memory_gb: 0.5 - disk_size_gb: 10 -network: - forwarded_ports: - - 80:1200 - - 443:1200 -# environment variables section, refer to Settings -env_variables: - CACHE_EXPIRE: '300' -# [END app_yaml] -``` - -#### Deploy to standard environment - -Under RSSHub's root directory, create a file `app.yaml` with the following content: - -```yaml -# [START app_yaml] -runtime: nodejs8 - -network: - forwarded_ports: - - 80:1200 - - 443:1200 -# environment variables section, refer to Settings -env_variables: - CACHE_EXPIRE: '300' -# [END app_yaml] -``` - -### Install - -Under RSSHub's root directory, execute the following commands to launch RSSHub - -```bash -gcloud app deploy -``` - -For changing the deployment project id or version id, please refer to `Deploying a service` section [here](https://cloud.google.com/appengine/docs/flexible/nodejs/testing-and-deploying-your-app). - -You can access your `Google App Engine URL` to check the deployment status - -## Play with Docker - -If you would like to test routes or avoid IP limits, etc., you may build your own RSSHub for free by clicking the button below. - -[![Try in PWD](https://raw.githubusercontent.com/play-with-docker/stacks/master/assets/images/button.png)](https://labs.play-with-docker.com/?stack=https://raw.githubusercontent.com/DIYgod/RSSHub/master/docker-compose.yml) - -::: warning Warning - -- [DockerHub](https://hub.docker.com) account required -- [Play with Docker](https://labs.play-with-docker.com/) instance will last for 4 hours at most. It should only be used for testing purpose -- If deploy success but port cannot be auto-deteced,please click the `open port` button on the top and type `1200` -- Sometimes PWD won't work as expected. If you encounter blank screen after `Start`, or some error during initialization, please retry - -::: - -## Configuration - -Configure RSSHub by setting environment variables - -### Network Configuration - -`PORT`: listening port, default to `1200` - -`SOCKET`: listening Unix Socket, default to `null` - -`LISTEN_INADDR_ANY`: open up for external access, default to `1` - -`REQUEST_RETRY`: retries allowed for failed requests, default to `2` - -`REQUEST_TIMEOUT`: milliseconds to wait for the server to end the response before aborting the request with error, default to `3000` - -`UA`: user agent, using random user agent (Chrome on macOS) by default - -`NO_RANDOM_UA`: disable random user agent, default to `null` - -### CORS Request - -RSSHub by default reject CORS requests. This behavior can be modified via setting `ALLOW_ORIGIN: *` or `ALLOW_ORIGIN: www.example.com`. - -### Cache Configurations - -RSSHub supports two caching methods: memory and redis - -`CACHE_TYPE`: cache type, `memory` or `redis`, empty this value will disable caching, default to `memory` - -`CACHE_EXPIRE`: route cache expiry time in seconds, default to `5 * 60` - -`CACHE_CONTENT_EXPIRE`: content cache expiry time in seconds, it will be recalculated when it is accessed, default to `1 * 60 * 60` - -`REDIS_URL`: Redis target address (invalid when `CACHE_TYPE` is set to memory), default to `redis://localhost:6379/` - -`MEMORY_MAX`: maximum number of cached items (invalid when `CACHE_TYPE` is set to redis), default to `256` - -### Proxy Configurations - -Partial routes have a strict anti-crawler policy, and can be configured to use proxy. - -Proxy can be configured via either **Proxy URI** or **Proxy options**. When both are configured, RSSHub will use the configuration in **Proxy URI**. - -#### Proxy URI - -`PROXY_URI`: Proxy supports socks4, socks5(hostname is resolved locally, not recommanded), socks5h(hostname is -resolved by the SOCKS server, recommanded, prevents DNS poisoning or DNS leak), http, https. See [socks-proxy-agent](https://www.npmjs.com/package/socks-proxy-agent) NPM package page. See also [cURL OOTW: SOCKS5](https://daniel.haxx.se/blog/2020/05/26/curl-ootw-socks5/). - -> Proxy URI's format: -> -> - `{protocol}://{host}:{port}` -> - `{protocol}://{username}:{password}@{host}:{port}` (with credentials) -> -> Some examples: -> -> - `socks4://127.0.0.1:1080` -> - `socks5h://user:pass@127.0.0.1:1080` (username as `user`, password as `pass`) -> - `socks://127.0.0.1:1080` (`socks5h` when protocol is `socks`) -> - `http://127.0.0.1:8080` -> - `http://user:pass@127.0.0.1:8080` -> - `https://127.0.0.1:8443` - -#### Proxy options - -`PROXY_PROTOCOL`: Using proxy, supports socks, http, https, etc. See [socks-proxy-agent](https://www.npmjs.com/package/socks-proxy-agent) NPM package page and [source](https://github.com/TooTallNate/node-socks-proxy-agent/blob/master/src/agent.ts) for what these protocols mean. See also [cURL OOTW: SOCKS5](https://daniel.haxx.se/blog/2020/05/26/curl-ootw-socks5/) for reference. - -`PROXY_HOST`: host or IP of the proxy - -`PROXY_PORT`: port of the proxy - -`PROXY_AUTH`: credentials to authenticate a user agent to proxy server, `Proxy-Authorization: Basic ${process.env.PROXY_AUTH}` - -`PROXY_URL_REGEX`: regex for url of enabling proxy, default to `.*` - -### User Authentication Configurations - -Routes in `protected_route.js` will be protected using HTTP Basic Authentication. - -When adding feeds using RSS readers with HTTP Basic Authentication support, authentication information is required, eg: . - -For readers that do not support HTTP Basic authentication, please refer to [Access Control Configuration](#access-control-configuration). - -`HTTP_BASIC_AUTH_NAME`: HTTP basic authentication username, default to `usernam3`, please change asap - -`HTTP_BASIC_AUTH_PASS`: HTTP basic authentication password, default to `passw0rd`, please change asap - -### Access Control Configuration - -RSSHub supports access control via access key/code, whitelisting and blacklisting, enabling any will activate access control for all routes. `ALLOW_LOCALHOST: true` will grant access to all localhost IP addresses. - -#### White/blacklisting - -- `WHITELIST`: the blacklist. When set, values in `BLACKLIST` are disregarded - -- `BLACKLIST`: the blacklist - -White/blacklisting support IP, route and UA as values, fuzzy matching. Use `,` as the delimiter to separate multiple values, eg: `WHITELIST=1.1.1.1,2.2.2.2,/qdaily/column/59` - -#### Access Key/Code - -- `ACCESS_KEY`: the access key. When set, access via the key directly or the access code described above - -Access code is the md5 generated based on the access key + route, eg: - -| Access key | Route | Generating access code | Access code | -| ----------- | ----------------- | ---------------------------------------- | -------------------------------- | -| ILoveRSSHub | /qdaily/column/59 | md5('/qdaily/column/59' + 'ILoveRSSHub') | 0f820530128805ffc10351f22b5fd121 | - -- Routes are accessible via `code`, eg: - -- Or using `key` directly, eg: - -See the relation between access key/code and white/blacklisting. - -| | Whitelisted | Blacklisted | Correct access key/code | Wrong access key/code | No access key/code | -| ----------- | ----------- | ----------- | ----------------------- | --------------------- | ------------------ | -| Whitelisted | ✅ | ✅ | ✅ | ✅ | ✅ | -| Blacklisted | ✅ | ❌ | ✅ | ❌ | ❌ | - -### Logging Configurations - -`DEBUG_INFO`: display route information on the homepage for debugging purposes. When set to neither `true` nor `false`, use parameter `debug` to enable display, eg: . Default to `true` - -`LOGGER_LEVEL`: specifies the maximum [level](https://github.com/winstonjs/winston#logging-levels) of messages to the console and log file, default to `info` - -`NO_LOGFILES`: disable logging to log files, default to `false` - -`SENTRY`: [Sentry](https://sentry.io) dsn, used for error tracking - -`SENTRY_ROUTE_TIMEOUT`: Report Sentry if route execution takes more than this milliseconds, default to `3000` - -### Image Processing - -::: tip New Config Format - -We are currently testing out a new format, providing end-user with more flexibility. For more info, please refer to [Parameters->Multimedia processing](/en/parameter.html#multimedia-processing). - -When using our new config, please leave the following environment vairable blank. By default, image hotlink template will be forced when present. - -::: - -`HOTLINK_TEMPLATE`: replace image URL in the description to avoid anti-hotlink protection, leave it blank to disable this function. Usage reference [#2769](https://github.com/DIYgod/RSSHub/issues/2769). You may use any property listed in [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL#Properties) (suffixing with `_ue` results in URL encoding), format of JS template literal. e.g. `${protocol}//${host}${pathname}`, `https://i3.wp.com/${host}${pathname}`, `https://images.weserv.nl?url=${href_ue}` - -`HOTLINK_INCLUDE_PATHS`: limit the routes to be processed, only matched routes will be processed. Set multiple values with comma `,` as delimiter. If not set, all routes will be processed - -`HOTLINK_EXCLUDE_PATHS`: exclude routes that do not need to be processed, all matched routes will be ignored. Set multiple values with comma `,` as delimiter. Can be used alone, or to exclude routes that are already included by `HOTLINK_INCLUDE_PATHS`. If not set, no routes will be ignored - -::: tip Route matching pattern - -`HOTLINK_INCLUDE_PATHS` and `HOTLINK_EXCLUDE_PATHS` match the root path and all recursive sub-paths of the route, but not substrings. Note that the path must start with `/` and end without `/`. - -e.g. `/example`, `/example/sub` and `/example/anthoer/sub/route` will be matched by `/example`, but `/example_route` will not be matched. - -It is also valid to contain route parameters, e.g. `/weibo/user/2612249974`. - -::: - -### Features - -::: tip Experimental features - -Configs in this sections are in beta stage, and are turn off by default. Please read corresponded description and turn on if necessary. - -::: - -`ALLOW_USER_HOTLINK_TEMPLATE`: [Parameters->Multimedia processing](/en/parameter.html#multimedia-processing) - -`FILTER_REGEX_ENGINE`: Define Regex engine used in [Parameters->filtering](/en/parameter.html#filtering). Valid value are `[re2, regexp]`. Default value is `re2`. We suggest public instance should leave this value to default, and this option right now is mainly for backward compatibility. - -### Other Application Configurations - -`DISALLOW_ROBOT`: prevent indexing by search engine, default to enable, set false or 0 to disable - -`ENABLE_CLUSTER`: enable cluster mode, default to `false` - -`NODE_ENV`: display error message on pages for authentication failing, default to `production` (i.e. no display) - -`NODE_NAME`: node name, used for load balancing, identify the current node - -`PUPPETEER_WS_ENDPOINT`: browser WebSocket endpoint which can be used as an argument to puppeteer.connect, refer to [browserWSEndpoint](https://pptr.dev/api/puppeteer.browser.wsendpoint) - -`CHROMIUM_EXECUTABLE_PATH`: path to the Chromium (or Chrome) executable. If puppeteer is not bundled with Chromium (manually skipped downloading or system architecture is arm/arm64), configuring this can effectively enable puppeteer. Or alternatively, if you prefer Chrome to Chromium, this configuration will help. **WARNING**: only effective when `PUPPETEER_WS_ENDPOINT` is not set; only useful for manual deployment, for Docker, please use the `chromium-bundled` image instead. - -`TITLE_LENGTH_LIMIT`: limit the length of feed title generated in bytes, an English alphabet counts as 1 byte, the rest such as Chinese, Japanese, Korean or Arabic counts as 2 bytes by design, default to `150` - -### Route-specific Configurations - -::: tip Notice - -Configs here are incomplete. - -See docs of the specified route and `lib/config.js` for detailed information. - -::: - -- Bitbucket: [Basic auth with App passwords](https://developer.atlassian.com/cloud/bitbucket/rest/intro/#basic-auth) - - - `BITBUCKET_USERNAME`: Your Bitbucket username - - `BITBUCKET_PASSWORD`: Your Bitbucket app password - -- Discuz cookie - - - `DISCUZ_COOKIE_{cid}`: Cookie of a forum powered by Discuz, cid can be anything from 00 to 99. When visiting a Discuz route, use cid to specify this cookie. - -- disqus: [API Key application](https://disqus.com/api/applications/) - - - `DISQUS_API_KEY`: Disqus API - -- douban - - - `DOUBAN_COOKIE`: Cookie of douban user - -- E-Hentai - - - `EH_IPB_MEMBER_ID`: The value of `ipb_member_id` in the cookie header after logging in E-Hentai - - `EH_IPB_PASS_HASH`: The value of `ipb_pass_hash` in the cookie header after logging in E-Hentai - - `EH_SK`: The value of `sk` in the cookie header after logging in E-Hentai - - `EH_STAR`: The value of `star` in the cookie header if your account has stars. If this value is set, image limit allocation will links to the account rather than IP address - - `EH_IGNEOUS`: The value of `igneous` in the cookie header after logging in ExHentai. If this value is set, RSS will be generated from ExHentai - - `EH_IMG_PROXY`: Cover proxy address. If this is set, the link to the cover image will be replaced with this value at the beginning. When using ExHentai, the cover image requires cookies to access it, so you can use this with a cookie-added proxy server to access the cover image without cookies in some readers. - -- Fantia - - - `FANTIA_COOKIE`: The `cookie` after login can be obtained by viewing the request header in the console, If not filled in will cause some posts that require login to read to get exceptions - -- GitHub: [Access Token application](https://github.com/settings/tokens) - - - `GITHUB_ACCESS_TOKEN`: GitHub Access Token - -- Google Fonts: [API key application](https://developers.google.com/fonts/docs/developer_api#a_quick_example) - - - `GOOGLE_FONTS_API_KEY`: API key - -- Instagram: - - - `IG_USERNAME`: Your Instagram username - - `IG_PASSWORD`: Your Instagram password - - `IG_PROXY`: Proxy URL for Instagram - - Warning: Two Factor Authentication is **not** supported. - -- Iwara: - - - `IWARA_COOKIE`: Cookie of Iwara User - -- Last.fm - - - `LASTFM_API_KEY`: Last.fm API Key - -- mail: - - - `EMAIL_CONFIG_{email}`: Mail setting, replace `{email}` with the email account, replace `@` and `.` in email account with `_`, e.g. `EMAIL_CONFIG_xxx_gmail_com`. The value is in the format of `password=password&host=server&port=port`, eg: - - Linux env: `EMAIL_CONFIG_xxx_qq_com="password=123456&host=imap.qq.com&port=993"` - - docker env: `EMAIL_CONFIG_xxx_qq_com=password=123456&host=imap.qq.com&port=993`, please do not include quotations `'`,`"` - -- Mastodon user timeline: apply API here `https://mastodon.example/settings/applications`(repalce `mastodon.example`), please check scope `read:search` - - - `MASTODON_API_HOST`: API instance domain - - `MASTODON_API_ACCESS_TOKEN`: user access token - - `MASTODON_API_ACCT_DOMAIN`: acct domain for particular instance - -- nhentai torrent: [Registration](https://nhentai.net/register/) - - - `NHENTAI_USERNAME`: nhentai username or email - - `NHENTAI_PASSWORD`: nhentai password - -- pixiv: [Registration](https://accounts.pixiv.net/signup) - - - `PIXIV_REFRESHTOKEN`: Please refer to [this article](https://gist.github.com/ZipFile/c9ebedb224406f4f11845ab700124362) to get a `refresh_token` - - `PIXIV_BYPASS_CDN`: bypass Cloudflare bot check by directly accessing Pixiv source server, defaults to disable, set `true` or `1` to enable - - `PIXIV_BYPASS_HOSTNAME`: Pixiv source server hostname or IP address, hostname will be resolved to IPv4 address via `PIXIV_BYPASS_DOH`, defaults to `public-api.secure.pixiv.net` - - `PIXIV_BYPASS_DOH`: DNS over HTTPS endpoint, it must be compatible with Cloudflare or Google DoH JSON schema, defaults to `https://1.1.1.1/dns-query` - - `PIXIV_IMG_PROXY`: Used as a proxy for image addresses, as pixiv images have anti-theft, default to `https://i.pixiv.cat` - -- pixiv fanbox: Get paid content - - - `FANBOX_SESSION_ID`: equals to `FANBOXSESSID` in site cookies. - -- Sci-hub for scientific journal routes: - - - `SCIHUB_HOST`: The Sci-hub mirror address that is accessible from your location, default to `https://sci-hub.se`. - -- Spotify: [API key registration](https://developer.spotify.com) - - - `SPOTIFY_CLIENT_ID`: Client ID of the application - - `SPOTIFY_CLIENT_SECRET`: Client secret of the application - -- Spotify (user data related routes): - - - `SPOTIFY_REFRESHTOKEN`: The refresh token of the user from the Spotify application. Check [this gist](https://gist.github.com/outloudvi/d1bbeb5e989db5385384a223a7263744) for detailed information. - -- Telegram: [Bot application](https://telegram.org/blog/bot-revolution) - - - `TELEGRAM_TOKEN`: Telegram bot token - -- Twitter: [Application creation](https://apps.twitter.com) - - - `TWITTER_CONSUMER_KEY`: Twitter Developer API key, support multiple keys, split them with `,` - - `TWITTER_CONSUMER_SECRET`: Twitter Developer API key secret, support multiple keys, split them with `,` - - `TWITTER_WEBAPI_AUTHORIZAION`: Twitter Web API authorization. If either of the above environment variables is not set, the Twitter Web API will be used. However, no need to set this environment var since every single user and guest share the same authorization token which has already been built into RSSHub. - - `TWITTER_TOKEN_{handler}`: The token generated by the corresponding Twitter handler, replace `{handler}` with the Twitter handler, the value is a combination of `Twitter API key, Twitter API key secret, Access token, Access token secret` connected by a comma `,`. Eg. `TWITTER_TOKEN_RSSHub=bX1zry5nG4d1RbESQbnADpVIo,2YrD8qo9sXbB8VlYfVmo1Qtw0xsexnOliU5oZofq7aPIGou0Xx,123456789-hlkUHFYmeXrRcf6SEQciP8rP4lzmRgMgwdqIN9aK,pHcPnfa28rCIKhSICUCiaw9ppuSSl7T2f3dnGYpSM0bod`. - -- Wordpress: - - - `WORDPRESS_CDN`: Proxy HTTP image link with HTTPS link. Consider using: - - | url | backbone | - | -------------------------------------- | ------------ | - | https://imageproxy.pimg.tw/resize?url= | akamai | - | https://images.weserv.nl/?url= | cloudflare | - | https://pic1.xuehuaimg.com/proxy/ | cloudflare | - | https://cors.netnr.workers.dev/ | cloudflare | - | https://netnr-proxy.openode.io/ | digitalocean | - -- YouTube: [API Key application](https://console.developers.google.com/) - - - All routes: - - `YOUTUBE_KEY`: YouTube API Key, support multiple keys, split them with `,` - - Extra requirements for subscriptions route: - - `YOUTUBE_CLIENT_ID`: YouTube API OAuth 2.0 client ID - - `YOUTUBE_CLIENT_SECRET`: YouTube API OAuth 2.0 client secret - - `YOUTUBE_REFRESH_TOKEN`: YouTube API OAuth 2.0 refresh token. Check [this gist](https://gist.github.com/Kurukshetran/5904e8cb2361623498481f4a9a1338aa) for detailed instructions. - -- ZodGame: - - - `ZODGAME_COOKIE`: Cookie of ZodGame User diff --git a/docs/en/joinus/pub-date.md b/docs/en/joinus/pub-date.md deleted file mode 100644 index 0435e5d8e96037..00000000000000 --- a/docs/en/joinus/pub-date.md +++ /dev/null @@ -1,55 +0,0 @@ -# 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 - -## No Date - -**Do not add a date** when a website does not provide one. The `pubDate` option should be left empty. - -## Standard - -`pubDate` 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 - -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: - -## 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: - -### Parse Date - -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` - -Please refer to Day.js GitHub description for specific parsing parameters - -```javascript -const { parseDate } = require('@/utils/parse-date'); - -const pubDate = parseDate('2020/12/30', 'YYYY/MM/DD'); -``` - -If you need to parse a relative date, use `parseRelativeDate`. - -```javascript -const { parseRelativeDate } = require('@/utils/parse-date'); - -const pubDate = parseRelativeDate('2 days ago'); -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: - -::: warning Warning -Now, the time will be converted to server time, which facilitates middleware processing. -::: - -```javascript -const timezone = require('@/utils/timezone'); - -const pubDate = timezone(new Date(), +8); -``` diff --git a/docs/en/joinus/quick-start.md b/docs/en/joinus/quick-start.md deleted file mode 100644 index 423504881aacab..00000000000000 --- a/docs/en/joinus/quick-start.md +++ /dev/null @@ -1,609 +0,0 @@ ---- -sidebar: auto ---- - -# Join Us - -We welcome all pull requests. Suggestions and feedback are also welcomed [here](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 - -Optional, source path, leave it blank and it will never match, only appears in `RSSHub for current website` - -e.g. the `source` for `Twitter User timeline` is `/:id` - -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` - -Please note that `source` can only match URL Path, if the parameters are in URL Param and URL Hash please use `target` - -#### target - -Optional, RSSHub path, leave it blank to not generate an RSSHub path - -The corresponding path in RSSHub docs, e.g. the `target` for `Twitter User timeline` is `/twitter/user/:id` - -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 - -Furthermore, if the source path does not match the desired parameters, we can use `target` as a function with `params`, `url` and `document` parameters - -`params` are the parameters matched by `source` in the previous step, `url` is the page url, `document` is the page document - -Note that the `target` method runs in a sandbox, any changes to `document` will not be reflected in the web page - -### RSSBud - -[RSSBud](https://github.com/Cay-Zhang/RSSBud) supports RSSHub Radar rules and will also be updated automatically, but please note that: - -- Using `'.'` subdomain allows RSSBud to support common mobile domains such as `m` / `mobile` - -- 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 - -### Additional Information - -Adding `radar="1"` in the RSSHub docs will show a `support browser extension` badge - -If RSSBud is also supported, adding `rssbud="1"` will show a `support rssbud` badge diff --git a/docs/en/joinus/script-standard.md b/docs/en/joinus/script-standard.md deleted file mode 100644 index 45994b749e24d4..00000000000000 --- a/docs/en/joinus/script-standard.md +++ /dev/null @@ -1,147 +0,0 @@ -# Script Standard - -::: warning Warning - -This standard is still WIP and may change over time, so please remember to check it often! - -::: - -When writing a new route, RSSHub will read the following in a folder : - -- `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 - -**The above files are mandatory for all routes** - -``` -├───lib/v2 -│ ├───furstar -│ ├─── templates -│ ├─── description.art -│ ├─── router.js -│ ├─── maintainer.js -│ ├─── radar.js -│ ├─── someOtherJs.js -│ └───test -│ └───someOtherScript -... -``` - -**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 - -`router.js` should export a method that provides a `@koa/router` object when we initialize the route - -### 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')); -}; -``` - -## Maintainer List - -`maintainer.js` should export an object from which we will retrieve the maintainer information when we get the path-related information, etc. - -- key: `@koa/router` Corresponding path -- value: Array, includes all maintainers' Github Username - -Github ID may be a better choice, but it is inconvenient to deal with the follow-up, so use GitHub Username for now. - -### Example - -```js -module.exports = { - '/characters/:lang?': ['NeverBehave'], - '/artists/:lang?': ['NeverBehave'], - '/archive/:lang?': ['NeverBehave'], -}; -``` - -`npm run build:maintainer` will generate a list of maintainers under `assets/build` - -## Radar Rules - -Writing style: - -**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.** - -### Example - -```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', - }, - ], - }, -}; -``` - -`npm run build:radar` will generate a complete `radar-rules.js` under `/assets/build/` - -## Template - -We currently require all routes, when rendering content with HTML such as `description`, **must** use the art engine for layout - -art documentation: - -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 - -```art -
- - {{ if link !== null }} - {{name}} - {{ else }} - {{name}} - {{ /if }} -
-``` - -```js -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 deleted file mode 100644 index 7c3c8d91e50b38..00000000000000 --- a/docs/en/joinus/use-cache.md +++ /dev/null @@ -1,5 +0,0 @@ -# Use 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. - - diff --git a/docs/en/journal.md b/docs/en/journal.md deleted file mode 100644 index f07097bccdb4e6..00000000000000 --- a/docs/en/journal.md +++ /dev/null @@ -1,367 +0,0 @@ ---- -pageClass: routes ---- - -# Scientific Journal - -## Academy of Management - -### Journal - - - -| Id | Title | -| --------- | ------------------------------------------ | -| annals | Academy of Management Annals | -| amd | Academy of Management Discoveries | -| amgblproc | Academy of Management Global Proceedings | -| amj | Academy of Management Journal | -| amle | Academy of Management Learning & Education | -| amp | Academy of Management Perspectives | -| amproc | Academy of Management Proceedings | -| amr | Academy of Management Review | - - - -## American Chemistry Society - -### Journal - - - -::: tip Tip - -See [Browse Content](https://pubs.acs.org) - -::: - - - -## arXiv - -### Search Keyword - - - -See [arXiv API User Manual](https://arxiv.org/help/api/user-manual) to find out all query statements. - -Fill in parameter `query` with content after `http://export.arxiv.org/api/query?`. - - - -## BioOne - -### Featured articles - - - -### Journals - - - -## caa.reviews - -### Book Reviews - - - -### Exhibition Reviews - - - -### Essays - - - -## Cell Journal - -### Current Issue - - - -| `:category` | Query Type | Route | -| :---------: | :---------------------: | ---------------------------------------------------------- | -| current | Current Issue (default) | [/cell/cell/current](https://rsshub.app/cell/cell/current) | -| inpress | Articles in press | [/cell/cell/inpress](https://rsshub.app/cell/cell/inpress) | - - - -### Cover Story - - - -Subscribe to the cover images of the Cell journals, and get the latest publication updates in time. - -Including 'cell', 'cancer-cell', 'cell-chemical-biology', 'cell-host-microbe', 'cell-metabolism', 'cell-reports', 'cell-reports-physical-science', 'cell-stem-cell', 'cell-systems', 'chem', 'current-biology', 'developmental-cell', 'immunity', 'joule', 'matter', 'molecular-cell', 'neuron', 'one-earth' and 'structure'. - - - -## eLife - -### Latest Research - Research by Subject - - - -## ELSEVIER - -### Journal - - - -### Special Issue - - - -## Google Scholar - -### Keywords Monitoring - - - -::: warning - -Google Scholar has strict anti-crawling mechanism implemented, the demo below doesn't guarantee availability. Please deploy your own instance as it might increase the stability. - -::: - -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). - - - -### Author Citations - - - -The parameter id in the route is the id in the URL of the user's Google Scholar reference page, for example `https://scholar.google.com/citations?hl=zh-CN&user=mlmE4JMAAAAJ` to `mlmE4JMAAAAJ` - - - -## IEEE Xplore - -### Journal - - - -### Journal (Papers within the recent 2 months) - - - -## INFORMS - -### Category - - - -## MDPI - -### Journal - - - -## MIT Technology Review - - - -### Topics - - - -| `:category_name` | Route | -| -------- | ----- | -| humans-and-technology | /technologyreview/humans-and-technology | -| election-2020 | /technologyreview/election-2020 | -| artificial-intelligence | /technologyreview/artificial-intelligence | -| biotechnology | /technologyreview/biotechnology | -| blockchain | /technologyreview/blockchain | -| climate-change | /technologyreview/climate-change | -| computing |/technologyreview/computing | -| tech-policy | /technologyreview/tech-policy | -| silicon-valley | /technologyreview/silicon-valley| -| smart-cities | /technologyreview/smart-cities| -| space | /technologyreview/space | - -## Nature Journal - -::: tip Tips - -You can get all short name of a journal from or [Journal List](#nature-journal-journal-list). - -::: - -### Latest Research - - - -| `:journal` | Full Name of the Journal | Route | -| :-----------: | :-------------------------: | ---------------------------------------------------------------------------------- | -| nature | Nature | [/nature/research/nature](https://rsshub.app/nature/research/nature) | -| nbt | Nature Biotechnology | [/nature/research/nbt](https://rsshub.app/nature/research/nbt) | -| neuro | Nature Neuroscience | [/nature/research/neuro](https://rsshub.app/nature/research/neuro) | -| ng | Nature Genetics | [/nature/research/ng](https://rsshub.app/nature/research/ng) | -| ni | Nature Immunology | [/nature/research/ni](https://rsshub.app/nature/research/ni) | -| nmeth | Nature Method | [/nature/research/nmeth](https://rsshub.app/nature/research/nmeth) | -| nchem | Nature Chemistry | [/nature/research/nchem](https://rsshub.app/nature/research/nchem) | -| 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. - 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 - - - -### News & Comment - - - -| `:journal` | Full Name of the Journal | Route | -| :-----------: | :-------------------------: | --------------------------------------------------------------------------------------------------------------------- | -| nbt | Nature Biotechnology | [/nature/news-and-comment/nbt](https://rsshub.app/nature/news-and-comment/nbt) | -| neuro | Nature Neuroscience | [/nature/news-and-comment/neuro](https://rsshub.app/nature/news-and-comment/neuro) | -| ng | Nature Genetics | [/nature/news-and-comment/ng](https://rsshub.app/nature/news-and-comment/ng) | -| ni | Nature Immunology | [/nature/news-and-comment/ni](https://rsshub.app/nature/news-and-comment/ni) | -| nmeth | Nature Method | [/nature/news-and-comment/nmeth](https://rsshub.app/nature/news-and-comment/nmeth) | -| nchem | Nature Chemistry | [/nature/news-and-comment/nchem](https://rsshub.app/nature/news-and-comment/nchem) | -| 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 - - - -### Cover Story - - - -Subscribe to the cover images of the Nature journals, and get the latest publication updates in time. - - - -### Nature News - - - -### Research Highlight - - - -::: warning Warning - -Only some journals are supported. - -::: - - - -### Journal List - - - -## Oxford University Press - -### Oxford Academic - -#### Journal - - - -## Proceedings of The National Academy of Sciences (PNAS) - -### Latest Articles - Articles by Topic - -### Proceedings of The National Academy of Sciences (PNAS) - Latest Articles - - - -- Using router (`/pnas/` + Topic of Interest) to query latest research paper for a certain topic from PNAS journal. - If the `:topic` parameter is blank, or equal to 'latest', then all the latest papers will return. - - - - - -## PubMed - -### Trending articles - - - -::: tip Tip - -For the parameter **filter**, the `filter` parameter in the URL should be split into a string by `,`, here is an example. - -In , the filter parameters are `simsearch1.fha`, `pubt.clinicaltrial`, and `pubt.randomizedcontrolledtrial`. Therefore, the filter corresponding to the route should be filled with `simsearch1.fha,pubt.clinicaltrial,pubt.randomizedcontrolledtrial`, and the route is [`/pubmed/trending/simsearch1.fha,pubt .clinicaltrial,pubt.randomizedcontrolledtrial`](https://rsshub.app/pubmed/trending/simsearch1.fha,pubt.clinicaltrial,pubt.randomizedcontrolledtrial) - -::: - - - -## Science Magazine - -### Current Issue - - - -| Short name | Full name of the journal | Route | -| :--------: | :----------------------------: | ---------------------------------------------------------------------------------- | -| science | Science | [/science/current/science](https://rsshub.app/science/current/science) | -| sciadv | Science Advances | [/science/current/sciadv](https://rsshub.app/science/current/sciadv) | -| sciimmunol | Science Immunology | [/science/current/sciimmunol](https://rsshub.app/science/current/sciimmunol) | -| scirobotics | Science Robotics | [/science/current/scirobotics](https://rsshub.app/science/current/scirobotics) | -| signaling | Science Signaling | [/science/current/signaling](https://rsshub.app/science/current/signaling) | -| stm | Science Translational Medicine | [/science/current/stm](https://rsshub.app/science/current/stm) | - -- Using route (`/science/current/` + "short name for a journal") to get current issue of a journal from AAAS. -- Leaving it empty (`/science/current`) to get update from Science. - - - -### Cover Story - - - -Subscribe to the cover images of Science journals, and get the latest publication updates in time. - -Including 'Science', 'Science Advances', 'Science Immunology', 'Science Robotics', 'Science Signaling' and 'Science Translational Medicine'. - - - -### First Release - - - -*only Science, Science Immunology and Science Translational Medicine have first release* - - - -## ScienceDirect - -### Journal - - - -## Scitation - -### Journal - - - -### Section - - - -## Springer - -### Journal - - - -## X-MOL Platform - -### Journal - - diff --git a/docs/en/live.md b/docs/en/live.md deleted file mode 100644 index f9e07504979244..00000000000000 --- a/docs/en/live.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -pageClass: routes ---- - -# Live - -## LiSA - -### News - - - -### Latest Discography - - - -## V LIVE - -### Board - - - -## Yoasobi Official - -### News & Biography - - - -### Live - - - -### Media - - diff --git a/docs/en/multimedia.md b/docs/en/multimedia.md deleted file mode 100644 index c22533f90fc4d2..00000000000000 --- a/docs/en/multimedia.md +++ /dev/null @@ -1,524 +0,0 @@ ---- -pageClass: routes ---- - -# Multimedia - -## 60-Second Science - Scientific American - - - -Full transcript support for better user experience. - -## 7mmtv - -### Category - - - -**Language** - -| English | 日本語 | 한국의 | 中文 | -| ------- | ------ | ------ | ---- | -| en | ja | ko | zh | - -**Category** - -| Chinese subtitles AV | Censored | Amateur | Uncensored | Asian self-timer | H comics | -| -------------------- | ------------- | --------------- | --------------- | ---------------- | ----------- | -| chinese_list | censored_list | amateurjav_list | uncensored_list | amateur_list | hcomic_list | - -| Chinese subtitles AV random | Censored random | Amateur random | Uncensored random | Asian self-timer random | H comics random | -| --------------------------- | --------------- | ----------------- | ----------------- | ----------------------- | --------------- | -| chinese_random | censored_random | amateurjav_random | uncensored_random | amateur_random | hcomic_random | - -**Server** - -| All Server | fembed(Full DL) | streamsb(Full DL) | doodstream | streamtape(Full DL) | avgle | embedgram | videovard(Full DL) | -| ---------- | --------------- | ----------------- | ---------- | ------------------- | ----- | --------- | ------------------ | -| all | 21 | 30 | 28 | 29 | 17 | 34 | 33 | - - - -### Maker - - - -**Language** - -| English | 日本語 | 한국의 | 中文 | -| ------- | ------ | ------ | ---- | -| en | ja | ko | zh | - -**Category and Id** - -When `amateurjav_makersr` as **Amateur** is chosen as **Category**, the available **ids** are: - -| Maker | Id | -| ------------------------- | ---- | -| シロウトTV(SIRO) | 1752 | -| ラグジュTV(LUXU) | 1586 | -| ナンパTV(200GANA) | 1751 | -| PRESTIGE PREMIUM(300MAAN) | 1318 | -| S-CUTE | 1069 | -| ARA | 1585 | - -When `uncensored_makersr` as **Uncensored** is chosen as **Category**, the available **ids** are: - -| Maker | Id | -| ---------------------------------- | --- | -| HEYZO | 17 | -| 東京熱(Tokyo Hot) | 29 | -| 一本道(1pondo) | 32 | -| カリビアンコム(Caribbeancom) | 30 | -| カリビアンコム PPV(Caribbeancompr) | 40 | -| 天然むすめ(10musume) | 31 | -| パコパコママ(pacopacomama) | 36 | -| ガチん娘!(Gachinco) | 35 | -| エッチな4610 | 34 | -| 人妻斬り0930 | 38 | -| エッチな0930 | 39 | -| トリプルエックス (XXX-AV) | 126 | -| FC2 | 37 | - - - -## 91porn - -::: tip Tips - -91porn has multiple backup domains, routes use the permanent domain by default. If the domain is not accessible, you can add `?domain=` to specify the domain to be used. If you want to specify the backup domain to , you can add `?domain=0122.91p30.com` to the end of all 91porn routes, then the route will become [`/91porn?domain=0122.91p30.com`](https://rsshub.app/91porn?domain=0122.91p30.com) - -::: - -### Hot Video Today - - - -| English | 简体中文 | 繁體中文 | -| -- | -- | -- | -| en_US | cn_CN | zh_ZH | - - - -### Author - - - -## 99% Invisible - -### Transcript - - - -## Bandcamp - -### Weekly - - - -### Tag - - - -### Upcoming Live Streams - - - -## Coomer - -### Artist - - - -### Recent Posts - - - -## EZTV - -::: tip - -Official RSS: - -::: - -### Lookup Torrents by IMDB ID - - - -## Hentaimama - -### Recent Videos - - - -## JavBus - -::: tip Language - -You can change the language of each route to the languages listed below. - -| English | 日本语 | 한국의 | 中文 | -| ------- | ------ | ------ | ---- | -| en | ja | ko | (leave it empty) | - -::: - -::: tip Tips - -JavBus has multiple backup domains, these routes use default domain . If the domain is unreachable, you can add `?domain=` to the end of the route to specify the domain to visit. Let say you want to use the backup domain , you can add `?domain=javsee.icu` to the end of the route, then the route will be [`/javbus/en?domain=javsee.icu`](https://rsshub.app/javbus?domain=javsee.icu) - -**Note**: **Western** has different domain than the main site, the backup domains are also different. The default domain is and you can add `?western_domain=` to the end of the route to specify the domain to visit. Let say you want to use the backup domain , you can add `?western_domain=javsee.one` to the end of the route, then the route will be [`/javbus/western/en?western_domain=javsee.one`](https://rsshub.app/javbus/western?western_domain=javsee.one) - -::: - -### Censored - Home - - - -### Censored - Genre - - - -For more genre, please visit [Censored - Genre](https://www.javbus.com/en/genre) - - - -### Censored - Actresses - - - -For more actresses, please visit [Censored JAV Idols](https://www.javbus.com/en/actresses) - - - -### Censored - Series - - - -### Censored - Studio - - - -### Censored - Label - - - -### Censored - Director - - - -### Censored - Search - - - -### Uncensored - Home - - - -### Uncensored - Genre - - - -For more genre, please visit [Uncensored - Genre](https://www.javbus.com/en/uncensored/genre) - - - -### Uncensored - Actresses - - - -For more actresses, please visit [Uncensored JAV Idols](https://www.javbus.com/en/uncensored/actresses) - - - -### Uncensored - Series - - - -### Uncensored - Studio - - - -### Uncensored - Search - - - -### Western - Home - - - -### Western - Genre - - - -For more genre, please visit [Genre - Video](https://www.javbus.org/en/genre) - - - -### Western - Actresses - - - -For more actresses [Western AV Idols](https://www.javbus.org/en/actresses) - - - -### Western - Series - - - -### Western - Studio - - - -### Western - Search - - - -## JAVLibrary - -### Recently Discussed Videos - - - -### New Releases - - - -| videos with comments (by date) | everything (by date) | -| ------------------------------ | -------------------- | -| 1 | 2 | - - - -### Recently Inserted Videos - - - -### Most Wanted Videos - - - -| Last Month | All Time | -| ---------- | -------- | -| 1 | 2 | - - - -### Best Rated Videos - - - -| Last Month | All Time | -| ---------- | -------- | -| 1 | 2 | - - - -### Best Reviews - - - -| Last Month | All Time | -| ---------- | -------- | -| 1 | 2 | - - - -### Videos by categories - - - -| videos with comments (by date) | everything (by date) | -| ------------------------------ | -------------------- | -| 1 | 2 | - -::: tip Tip - -See [Categories](https://www.javlibrary.com/en/genres.php) to view all categories. - -::: - - - -### Videos by star - - - -| videos with comments (by date) | everything (by date) | -| ------------------------------ | -------------------- | -| 1 | 2 | - -::: tip Tip - -See [Ranking](https://www.javlibrary.com/en/star_mostfav.php) to view stars by ranks. - -See [Directory](https://www.javlibrary.com/en/star_list.php) to view all stars. - -::: - - - -### Posts published by user - - - -### Videos by user - - - -| Wanted | Watched | Owned | -| ---------- | ----------- | --------- | -| userwanted | userwatched | userowned | - - - -## Melon - -### Chart - - - -| 24H | 일간 | 주간 | 월간 | -| --- | ---- | ---- | ----- | -| | day | week | month | - - - -## Nyaa - -### Search Result - - - -### User - - - -### Sukebei Search Result - - - -### Sukebei User - - - -## PornHub - -### Category - - - -### Keyword Search - - - -### Users - - - -### Verified amateur / Model - - - -### Verified model / Pornstar - - - -**`sort`** - -| mr | mv | tr | lg | cm | -| ----------- | ----------- | --------- | ------- | ------ | -| Most Recent | Most Viewed | Top Rated | Longest | Newest | - -### Video List - - - -**`language`** - -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 ; - -- `jp` (Japanese), for Pornhub in Japan etc. - -## PRESTIGE(プレステージ) - -### シリーズ - - - -| 人気順 | 新着順 | 発売日順 | タイトル順 | 価格の安い順 | 価格の高い順 | -| ------- | ------ | ---- | ----- | ---- | ---- | -| popular | near | date | title | low | high | - - - -## s-hentai - -### Category - - - -| Doujin | HCG | Games・Animes | Voices・ASMR | Ready to Download | -| ------ | --- | ------------- | ------------ | ----------------- | -| 1 | 2 | 3 | 4 | ready-to-download | - - - -## Sankaku Complex - -### Post - - - -## SoundCloud - -### Tracks - - - -## Spotify - -### Artist Albums - - - -### Playlist - - - -### Personal Saved Tracks - - - -### Personal Top Tracks - - - -### Personal Top Artists - - - -## Trakt.tv - -### User Collection - - - -## u3c3 - -### Keyword Search - - - -### Type - - - -## YouTube - -Refer to [#youtube](/en/social-media.html#youtube) diff --git a/docs/en/new-media.md b/docs/en/new-media.md deleted file mode 100644 index 82b5783831e418..00000000000000 --- a/docs/en/new-media.md +++ /dev/null @@ -1,805 +0,0 @@ ---- -pageClass: routes ---- - -# New media - -## 9To5 - -### 9To5 Sub-site - - - -Supported sub-sites: -| 9To5Mac | 9To5Google | 9To5Toys | -| ------- | ---------- | -------- | -| Mac | Google | Toys | - - - -## AEON - - - -Subscribe it by channel: -| Ideas | Essays | Videos | -| ----- | ------ | ------ | -| ideas | essays | videos | - -Subscribe it by subject or topic : -| Culture | Philosophy | Psychology | Society | Science | -| ------- | ---------- | ---------- | ------- | ------- | -| culture | philosophy | psychology | society | science | - -Compared to the official one, the RSS feed generated by RSSHub not only has more fine-grained options, but eliminates pull quotes which can't be easily distinguished from other paragraphs by any RSS reader but purely disrupts the reading flow. Besides that, this feed also provides users a bio of the author in the end of the article. - - - -## American Federation of Labor and Congress of Industrial Organizations - -### Blog - - - -## AppleInsider - -### Category - - - -| News | Reviews | How-tos | -| ---- | ------- | ------- | -| | reviews | how-to | - - - -## ASML Holding N.V. - -### Press releases & announcements - - - -## Bell Labs - -### Event and News - - - -| Featured events | Latest recognition | Press releases | -| ------- | ---------- | -------- | -| events | industry-recognition | press-releases | - - - -## biodiscover.com - -### Channel - - - -| Research | Interview | Industry | Activity | -| -------- | --------- | -------- | -------- | -| reaseach | interview | industry | activity | - - - -## BOF - -### Home - - - -## cfan - -### News - - - -## CGTN - -### Opinions - - - -### Most Read & Most Share - - - -### Top News - - - -### Editors' Pick - - - -## China Labour Bulletin - -### Commentary and Analysis - - - -## China Labour Watch - -### Reports - - - -| All | Automotive | Cookware | Electronics | Footwear | Furniture | Garment | General | Printing | Retail | Toys | -| - | - | - | - | - | - | - | - | - | - | - | -| | 2 | 6 | 14 | 3 | 4 | 10 | 8 | 1 | 9 | 7 | - - - -## China.com - -### Military - Military News - - - - -### News and current affairs - - - -Category of news - -| China News | International News | Social News | Breaking News | -| -------- | ------------- | ------ | ------- | -| domestic | international | social | news100 | - - - -## Common App - -### Blog - - - -## Day One - -### Blog - - - -## DeepMind - -### Blog - - - -## Engadget - -### Chinese - - - -### Multi-language - - - -| Traditional Chinese | Simplified Chinese | US | Japanese | -| ------------------- | ------------------ | --- | -------- | -| chinese | cn | us | japanese | - - - -## EU Disinfo Lab - -### Publications - - - -## Europa Press - -### Category - - - -| España | Internacional | Economía | Deportes | -| -------- | ------------- | -------- | -------- | -| nacional | internacional | economía | deportes | - -| Cultura | Sociedad | Ciencia | Salud | -| ------- | -------- | ------- | ----- | -| cultura | sociedad | ciencia | salud | - -| Tecnología | Comunicados | Estar donde estés | -| ---------- | ----------- | ----------------- | -| tecnología | comunicados | estar-donde-estes | - -| Andalucía | Aragón | Cantabria | Castilla-La Mancha | -| --------- | ------ | --------- | ------------------ | -| andalucia | aragon | cantabria | castilla-lamancha | - -| Castilla y León | Cataluña | Extremadura | Galicia | -| --------------- | --------- | ----------- | ------- | -| castilla-y-leon | catalunya | extremadura | galicia | - -| Islas Canarias | Islas Baleares | Madrid | País Vasco | -| -------------- | -------------- | ------ | ---------- | -| islas-canarias | illes-balears | madrid | euskadi | - -| La Rioja | C. Valenciana | Navarra | Asturias | -| -------- | -------------------- | ------- | -------- | -| la-rioja | comunitat-valenciana | navarra | asturias | - -| Murcia | Ceuta y Melilla | -| ------ | --------------- | -| murcia | ceuta-y-melilla | - - - -## ezone.hk - -### Category - - - -| 科技焦點 | 網絡生活 | 教學評測 | IT Times | -| -------- | -------- | -------- | -------- | -| srae001 | srae008 | srae017 | srae021 | - - - -## Fashion Network - -### Headline - - - -### News - - - -Sectoies - -Fashion 1 - -| Ready-to-wear | Accessories | Footwear | Sports | Denim | Lingerie | Swimwear | Eyewear | Bridal wear | Textile | Miscellaneous | -| ------------- | ----------- | -------- | ------ | ----- | -------- | -------- | ------- | ----------- | ------- | ------------- | -| 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 31 | - -Luxury 2 - -| Ready-to-wear | Accessories | Footwear | Watches | Jewellery | Miscellaneous | -| ------------- | ----------- | -------- | ------- | --------- | ------------- | -| 15 | 16 | 17 | 18 | 19 | 32 | - -Beauty 3 - -| Perfume | Cosmetics | Aesthetics | Wellness | Hair | Miscellaneous | -| ------- | --------- | ---------- | -------- | ---- | ------------- | -| 21 | 22 | 23 | 24 | 33 | | - -Lifestyle 4 - -| Home decor | Tableware | Hospitality | Fine foods | Tourism | Miscellaneous | -| ---------- | --------- | ----------- | ---------- | ------- | ------------- | -| 25 | 26 | 27 | 28 | 29 | 34 | - -Others 30 - -Category - -| Retail | Business | Industry | Trade shows | -| ------ | -------- | -------- | ----------- | -| 15 | 112 | 5 | 12 | - -| Innovations | Collection | Catwalks | Design | -| ----------- | ---------- | -------- | ------ | -| 113 | 114 | 60 | 70 | - -| Media | Campaigns | People | Events | Appointments | -| ----- | --------- | ------ | ------ | ------------ | -| 50 | 115 | 80 | 90 | 95 | - -Country - -| Latin America | Brazil | China | France | -| ------------- | ------ | ----- | ------ | -| pe | br | cn | fr | - -| Germany | India | Italy | Japan | -| ------- | ----- | ----- | ----- | -| de | in | it | jp | - -| Mexico | Portugal | Russia | Spain | -| ------ | -------- | ------ | ----- | -| mx | pt | ru | es | - -| Turkey | United Kingdom | USA | Worldwide | -| ------ | -------------- | --- | --------- | -| tr | uk | us | ww | - - - -## Fermilab - -### News - - - -| All News | Fermilab features | Press releases | Symmetry features | -| -------- | ----------------- | -------------- | ----------------- | -| allnews | 269 | 55 | 12580 | - - - -## Global Disinformation Index - -### Research - - - -### Blog - - - -## Google News - -### News - - - -## Grub Street - -### Posts - - - -## Harvard Business Review - -### Topic - - - -| LATEST | POPULAR | FROM THE STORE | FOR YOU | -| ------ | ------- | -------------- | ------- | -| Latest | Popular | From the Store | For You | - -::: tip Tip - -Click here to view [All Topics](https://hbr.org/topics) - -::: - - - -## Harvard Health Publishing - -### Harvard Health Blog - - - -## iDownloadBlog - -### iDownloadBlog - - - -Provides a better reading experience (full text articles) over the official one. - - - -## Indians in Kuwait - -### News - - - -## Institute of International Education - -### Blog - - - -## International Energy Agency - -### News and events - - - -| Featured | News | Calendar | Past events | -| --------------- | ---- | -------- | ----------- | -| news-and-events | news | calendar | past-events | - - - -## International Mathematical Union - -### Fields Medal - - - -## KBS - -### News - - - -| 한국어 | عربي | 中国语 | English | Français | Deutsch | Bahasa Indonesia | 日本語 | Русский | Español | Tiếng Việt | -| ------ | ---- | ------ | ------- | -------- | ------- | ---------------- | ------ | ------- | ------- | ---------- | -| k | a | c | e | f | g | i | j | r | s | v | - - - -### Today - - - -| 한국어 | عربي | 中国语 | English | Français | Deutsch | Bahasa Indonesia | 日本語 | Русский | Español | Tiếng Việt | -| ------ | ---- | ------ | ------- | -------- | ------- | ---------------- | ------ | ------- | ------- | ---------- | -| k | a | c | e | f | g | i | j | r | s | v | - - - -## Kuwait Local - -### Latest News - - - -### Categorised News - - - -## Letterboxd - -### User diary - - - -### Following diary - - - -## Line - -### Today - - - -Edition - -| Taiwan | Thailand | Indonesia | Hong Kong | -| ------ | -------- | --------- | --------- | -| tw | th | id | hk | - - - -## Macfilos - -### Blog - - - -## MakeUseOf - - - -## Matters - -### Latest - - - -### Tags - - - -### Author - - - -## Mirror - -### User - - - -## National Association of Colleges and Employers - -### Blog - - - -| Most Recent | Top Rated | Most Read | -| - | - | - | -| | top-blogs | mostreadblogs | - - - -## Nautilus - -### Topics - - - -This route provides a flexible plan with full text content to subscribe specific topic(s) on the Nautilus. Please visit [nautil.us](http://nautil.us) and click `Topics` to acquire whole topic list. - - - -## NL Times - -### News - - - -| Top Stories (default) | Health | Crime | Politics | Business | Tech | Culture | Sports | Weird | 1-1-2 | -| --------------------- | ------ | ----- | -------- | -------- | ---- | ------- | ------ | ----- | ----- | -| top-stories | health | crime | politics | business | tech | culture | sports | weird | 1-1-2 | - - - -## Oak Ridge National Laboratory - -### News - - - -## OpenAI - -### Blog - - - -| All | Research | Announcements | Events | Milestones | -| --- | -------- | ------------- | ------ | ---------- | -| | research | announcements | events | milestones | - - - -## Polar - -### Blog - - - -## Quanta Magazine - -### Archive - - - -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). - - - -## Radio-Canada.ca - -### Latest News - - - -| Français | English | Español | 简体中文 | 繁體中文 | العربية | ਪੰਜਾਬੀ | Tagalog | -| -------- | ------- | ------- | -------- | -------- | ------- | ------ | ------- | -| fr | en | es | zh-hans | zh-hant | ar | pa | tl | - - - -## Research Gate - -### Publications - - - -## RSS3 - -### Blog - - - -## Sakamichi Series - -### Nogizaka46 News - - - -### Nogizaka46 Blog - - - -### Keyakizaka46 News - - - -### Keyakizaka46 Blog - - - -### Sakurazaka46 News - - - -### Sakurazaka46 Blog - - - -| Member ID | Name | -| -- | ------ | -| 03 | 上村 莉菜 | -| 04 | 尾関 梨香 | -| 06 | 小池 美波 | -| 07 | 小林 由依 | -| 08 | 齋藤 冬優花 | -| 11 | 菅井 友香 | -| 14 | 土生 瑞穂 | -| 15 | 原田 葵 | -| 43 | 井上 梨名 | -| 53 | 遠藤 光莉 | -| 54 | 大園 玲 | -| 55 | 大沼 晶保 | -| 56 | 幸阪 茉里乃 | -| 44 | 関 有美子 | -| 45 | 武元 唯衣 | -| 46 | 田村 保乃 | -| 47 | 藤吉 夏鈴 | -| 57 | 増本 綺良 | -| 48 | 松田 里奈 | -| 50 | 森田 ひかる | -| 58 | 守屋 麗奈 | -| 51 | 山﨑 天 | - - - -### Hinatazaka46 News - - - -### Hinatazaka46 Blog - - - -## Samsung - -### Research Blog - - - -## Semiconductor Industry Association - -### Latest News - - - -## Sensor Tower - -### Blog - - - -| English | Chinese | Japanese | Korean | -| ------- | ------- | -------- | ------ | -| | zh-CN | ja | ko | - - - -## Simons Foundation - -### Articles - - - -### What We’re Reading - - - -## Sixth Tone - -### News - - - -## Sky Sports - -### News - -\ - -## Soomal - -### 话题 - - - -- Available languages: - -| Simplified Chinese | Traditional Chinese | English | -| ------------------ | ------------------- | ------- | -| zh | zh_tw | en | - -- Available topics by locale: - -| Languages | | | | | | -| ------------------- | -------- | ----- | -------- | -------- | -------- | -| Simplified Chinese | 最新文章 | 科普 | 测评报告 | 发烧入门 | 摄影入门 | 古典音乐入门 | -| 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) - - - -## SupChina - -### Feed - - - -### Podcasts - - - -## swissinfo - -### Category - - - -## The Brain - -### Blog - - - -| Blog | Recorded Events | Big Thinkers | -| ---- | ---- | ---- | -| blog | recorded-events | big-thinkers | - - - -## The Partnership on AI - -### Resources - - - -## The Verge - -### The Verge - - - -Provides a better reading experience (full text articles) over the official one. - - - -## Thrillist - - - -Provides all of the Thrillist articles with the specified tag. - - - -## Tribal Football - -### Latest News - - - -## VOA News - -### Day in Photos - - - -## Vulture - - - -## World Happiness - -### Blog - - - -### Archive - - diff --git a/docs/en/other.md b/docs/en/other.md deleted file mode 100644 index 1b238ff733f46e..00000000000000 --- a/docs/en/other.md +++ /dev/null @@ -1,269 +0,0 @@ ---- -pageClass: routes ---- - -# Uncategorized - -## Apple - -### Exchange and Repair Extension Programs - - - -### App Store/Mac App Store - -See [#app-store-mac-app-store](/en/program-update.html#app-store-mac-app-store) - -## AutoTrader - -### Search - - - -1. Conduct a search with desired filters on AutoTrader -1. Copy everything in the URL after `?`, for example: `https://www.autotrader.co.uk/car-search?radius=50&postcode=sw1a1aa&onesearchad=Used&onesearchad=Nearly%20New&onesearchad=New&price-to=9000&year-from=2012&body-type=Hatchback&transmission=Automatic&exclude-writeoff-categories=on` will produce `radius=50&postcode=sw1a1aa&onesearchad=Used&onesearchad=Nearly%20New&onesearchad=New&price-to=9000&year-from=2012&body-type=Hatchback&transmission=Automatic&exclude-writeoff-categories=on` - - - -## checkee.info - -### US Visa check status - - - -## Corona Virus Disease 2019 - -### South China Morning Post - Coronavirus outbreak - - - -### Macao Pagina Electrónica Especial Contra Epidemias: What’s New - -Official Website: [https://www.ssm.gov.mo/apps1/PreventWuhanInfection/en.aspx](https://www.ssm.gov.mo/apps1/PreventWuhanInfection/en.aspx) - - - -| Chinese | English | Portuguese | -| ------- | ------- | ---------- | -| ch | en | pt | - -### Singapore Ministry of Health - Past Updates on 2019-nCov Local Situation in Singapore - - - -### Yahoo Japan COVID19 news collection - -Official Website: - - - -## Darwin Awards - -### Award Winners - - - -## dcinside - -### board - - - -## DHL - -### DHL express - - - -## Email - -### Email list - -> Only support IMAP protocol, email password and other settings refer to [Email setting](/en/install) - - - -## Emi Nitta official website - -### Recent update - - - -### News - - - -## Fisher Spb - -### News - - - -## HackerOne - -### HackerOne Hacker Activity - - - - -## Instapaper - -### Personal sharing - - - -## Japanpost - -### Track & Trace Service - - - -| Japanese | English | -| -------- | ------- | -| ja | en | - - - -## King Arthur - -### Baking - - - -| Story | Recipes | Tips and Techniques | -| ----- | ------- | ------------------- | -| story | recipes | tips-and-techniques | - - - -## Lever - -### Recruitment - - - -## MITRE - -### All Publications - - - -## Mozilla - -### Firefox Monitor - - - -## Nobel Prize - -### List - - - -| Physics | Chemistry | Physiology or Medicine | Literature | Peace | Economic Science | -| ------- | --------- | ---------------------- | ---------- | ----- | ----------------- | -| physics | chemistry | physiology-or-medicine | literature | peace | economic-sciences | - - - -## Parcel Tracking - -### Hermes UK - - - -## Pocket - -### Trending - - - -## Product Hunt - -> The official feed: [https://www.producthunt.com/feed](https://www.producthunt.com/feed) - -### Today Popular - - - - -## Remote.work - -### Remote.work Job Information - - - -| All Jobs | Development | Design | Operation | Product | Other | Marketing | Sales | -| :------: | :---------: | :----: | :-------: | :-----: | :---: | :-------: | :---: | -| all | development | design | operation | product | other | marketing | sales | - - - -## SANS Institute - -### Latest conference materials - - - -## TransferWise - -### FX Pair Yesterday - - - -Refer to [the list of supported currencies](https://transferwise.com/tools/exchange-rate-alerts/). - - - -## Trending Search Keyword Aggregator - -### Aggregated Keyword Tracker - -Track entries containing specific keywords on major social media platforms. - -Current listings: *Weibo Search*、*Toutiao Search*、*Zhihu Search*、*Zhihu Videos*、*Zhihu Topics*。 - -Data Source: [trending-in-one](https://github.com/huqi-pr/trending-in-one) - - - -## TSSstatus (iOS downgrade channel) - -### Status - - - -Board and Build can be found in [here](http://api.ineal.me/tss/status) - - - -## wikiHow - -### Home - - - -### Category - - - -Top category can be found in [category Page](https://zh.wikihow.com/Special:CategoryListing), support secondary directories - -Type - -| All | Recommend | -| --- | --------- | -| all | rec | - - - -## 裏垢女子まとめ - -### Homepage - - - -### User - - - diff --git a/docs/en/parameter.md b/docs/en/parameter.md deleted file mode 100644 index 3db033920f9441..00000000000000 --- a/docs/en/parameter.md +++ /dev/null @@ -1,166 +0,0 @@ -# Parameters - -::: tip - -Parameters here are actually URI query and can be linked together with `&` to generate a complex feed. - -Parameters here need to be placed after the route path. Some routes may have **custom route parameters** and **parameters here** need to be placed after them. - -E.g. - -https://rsshub.app/twitter/user/durov/readable=1&includeRts=0?brief=100&limit=5 - -If a **output format** (`.atom`, `.rss`, `.debug.json`) is set, it needs to be placed between the route path (including **custom route parameters**) and **other parameters**. - -E.g. - -https://rsshub.app/twitter/user/durov/readable=1&includeRts=0.atom?brief=100&limit=5 - -::: - -## Filtering - -::: warning Warning - -Please make sure you've [fully URL-encoded](https://gchq.github.io/CyberChef/#recipe=URL_Encode(true)) the parameters. Do not rely on the browser's automatic URL encoding. Some characters, such as `+`, `&`, will not be automatically encoded, resulting in the final parsing result not being correct. - -::: - -::: warning Warning - -filter supports Regex, and due to the fact that some Regex are vulnerable to DoS (ReDoS), default engine `re2` blocks some of these functionalities available in node `Regexp`. These two engines also behaves a bit different in some corner cases. [Details](https://github.com/uhop/node-re2#limitations-things-re2-does-not-support) - - -If you need to use a different engine, please refer to [Deploy->Features->FILTER_REGEX_ENGINE](/en/install/#configuration-features). - -::: - - -The following URL query parameters are supported, Regex support is built-in. - -Set `filter` to include the content - -- `filter`: filter `title` and description - -- `filter_title`: filter `title` only - -- `filter_description`: filter `description` only - -- `filter_author`: filter `author` only - -- `filter_category`: filter `category` only - -- `filter_time`: filter `pubDate`, in seconds, return specified time range. Item without `pubDate` will not be filtered. - -E.g. [https://rsshub.app/dribbble/popular?filter=Blue|Yellow|Black](https://rsshub.app/dribbble/popular?filter=Blue|Yellow|Black) - -Set `filterout` to exclude unwanted content. - -- `filterout`: filter `title` and description - -- `filterout_title`: filter `title` only - -- `filterout_description`: filter `description` only - -- `filterout_author`: filter `author` only - -- `filterout_category`: filter `category` only - -E.g. [https://rsshub.app/dribbble/popular?filterout=Blue|Yellow|Black](https://rsshub.app/dribbble/popular?filterout=Blue|Yellow|Black) - -Set `filter_case_sensitive` to determine whether the filtering keywords should be case sensitive. The parameter would apply to both `filter` and `filterout`. - -Default: `true` - -E.g. [https://rsshub.app/dribbble/popular?filter=BluE|yeLLow|BlaCK&filter_case_sensitive=false](https://rsshub.app/dribbble/popular?filter=BluE|yeLLow|BlaCK&filter_case_sensitive=false) - -## Limit Entries - -Set `limit` to limit the number of articles in the feed. - -E.g. Dribbble Popular Top 10 [https://rsshub.app/dribbble/popular?limit=10](https://rsshub.app/dribbble/popular?limit=10) - -## Sorted - -Set `sorted` to control whether to sort the output by the publish date (`pubDate`). This is useful for some feeds that pin some entries at the top. Default to `true` i.e. the output is sorted. - -E.g. NJU Undergraduate Bulletin Board - -## Fulltext - -Enable fulltext via `mode` parameter. - -E.g. Bilibili article - -## Access Control - -Set `key` or `code` to grant access to requests. See [Access Control Configuration](install/#configuration-access-control-configuration-access-key-code). - -## Telegram Instant View - -Replace website link with Telegram's Instant View link. - -Enable Telegram Instant View requires a page template, it can be obtained from Telegram's [Instant View page](https://instantview.telegram.org/) - -- `tgiv`: template hash, obtained from the link of template page generated(the string after `&rhash=`) - -E.g. - -## Sci-hub link - -Output Sci-hub link in scientific journal routes, this supports major journals or routes that output DOIs. - -- `scihub`: set to any value - -E.g. - -## Conversion between Traditional and Simplified Chinese - -- `opencc`: `s2t` (Simplified Chinese to Traditional Chinese)、`t2s` (Traditional Chinese to Simplified Chinese), other optional values refer to [simplecc-wasm - Configurations](https://github.com/fengkx/simplecc-wasm#%E9%85%8D%E7%BD%AE-configurations) - -E.g. - -## Multimedia processing - -::: warning Warning - -This is an experimental API - -`image_hotlink_template` and `multimedia_hotlink_template` allow users to supply templates to replace media URLs. Certain routes plus certain RSS readers may result in users needing these features, but it's not very common. Vulnerable characters will be escaped automatically, making XSS attack impossible. The scope of URL replacement is limited to media elements, making any script URL unable to load and unable to cause XSS. As a result, users can only take the control of "where are the media from". These features are commonly side-effect-free. To enable these two parameters, please set `ALLOW_USER_HOTLINK_TEMPLATE` to `true` - -::: - -- `image_hotlink_template`: replace image URL in the description to avoid anti-hotlink protection, leave it blank to disable this function. Usage reference [#2769](https://github.com/DIYgod/RSSHub/issues/2769). You may use any property listed in [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL#Properties) (suffixing with `_ue` results in URL encoding), format of JS template literal. e.g. `${protocol}//${host}${pathname}`, `https://i3.wp.com/${host}${pathname}`, `https://images.weserv.nl?url=${href_ue}` -- `multimedia_hotlink_template`: the same as `image_hotlink_template` but apply to audio and video. Note: the service must follow redirects, allow reverse-proxy for audio and video, and must drop the `Referer` header when reverse-proxying. [Here is an easy-to-deploy project that fits these requirements](https://github.com/Rongronggg9/rsstt-img-relay). The project accepts simple URL concatenation, e.g. `https://example.com/${href}`, in which `example.com` should be replaced with the domain name of the service you've deployed -- `wrap_multimedia_in_iframe`: wrap audio and video in ``, + ], + desc: ' - Powered by RSSHub', + }, + relayed: { + items: [ + ` + + + +`, + ], + desc: ' - Powered by RSSHub', + }, + partlyRelayed: { + items: [ + ` + + + +`, + ], + desc: ' - Powered by RSSHub', + }, + }, + extraComplicated: { + origin: { + items: [ + { + content: + '\n\n\n\n\n\n\n\n\n\n', + itunes: {}, + }, + { + content: '\n', + itunes: {}, + }, + { + content: + '\n\n', + enclosure: { + url: 'https://mock.com/DIYgod/RSSHub.png', + type: 'image/png', + }, + itunes: { + image: 'https://mock.com/DIYgod/RSSHub.gif', + }, + }, + ], + image: { + link: 'https://github.com/DIYgod/RSSHub', + url: 'https://mock.com/DIYgod/RSSHub.png', + title: 'Test complicated', + }, + description: ' - Powered by RSSHub', + }, + processed: { + items: [ + { + content: + '\n\n\n\n\n\n\n\n\n\n', + itunes: {}, + }, + { + content: '\n', + itunes: {}, + }, + { + content: + '\n\n', + enclosure: { + url: 'https://i3.wp.com/mock.com/DIYgod/RSSHub.png', + type: 'image/png', + }, + itunes: { + image: 'https://i3.wp.com/mock.com/DIYgod/RSSHub.gif', + }, + }, + ], + image: { + link: 'https://github.com/DIYgod/RSSHub', + url: 'https://i3.wp.com/mock.com/DIYgod/RSSHub.png', + title: 'Test complicated', + }, + description: ' - Powered by RSSHub', + }, + urlencoded: { + items: [ + { + content: + '\n\n\n\n\n\n\n\n\n\n', + itunes: {}, + }, + { + content: '\n', + itunes: {}, + }, + { + content: + '\n\n', + enclosure: { + url: 'https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.png', + type: 'image/png', + }, + itunes: { + image: 'https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.gif', + }, + }, + ], + image: { + link: 'https://github.com/DIYgod/RSSHub', + url: 'https://images.weserv.nl?url=https%3A%2F%2Fmock.com%2FDIYgod%2FRSSHub.png', + title: 'Test complicated', + }, + description: ' - Powered by RSSHub', + }, + }, + extraMultimedia: { + origin: { + items: [ + { + content: + '\n\n\n\n', + }, + { + content: '\n', + enclosure: { + url: 'https://mock.com/DIYgod/RSSHub.mp4', + type: 'video/mp4', + }, + }, + ], + description: ' - Powered by RSSHub', + }, + relayed: { + items: [ + { + content: + '\n\n\n\n', + }, + { + content: '\n', + enclosure: { + url: 'https://i3.wp.com/mock.com/DIYgod/RSSHub.mp4', + type: 'video/mp4', + }, + }, + ], + description: ' - Powered by RSSHub', + }, + partlyRelayed: { + items: [ + { + content: + '\n\n\n\n', + }, + { + content: '\n', + enclosure: { + url: 'https://i3.wp.com/mock.com/DIYgod/RSSHub.mp4', + type: 'video/mp4', + }, + }, + ], + description: ' - Powered by RSSHub', + }, + }, +}; + +const testAntiHotlink = async (path, expectObj, query?: string | Record) => { + const app = (await import('@/app')).default; + + let queryStr; + if (query) { + queryStr = + typeof query === 'string' + ? query + : Object.entries(query) + .map(([key, value]) => `${key}=${value}`) + .join('&'); + } + path = path + (queryStr ? `?${queryStr}` : ''); + + const response = await app.request(path); + const parsed = await parser.parseString(await response.text()); + expect({ + items: parsed.items.slice(0, expectObj.items.length).map((i) => i.content), + desc: parsed.description, + }).toStrictEqual(expectObj); + + return parsed; +}; + +const testAntiHotlinkExtra = async (path, expectObj, query?: string | Record) => { + const app = (await import('@/app')).default; + + path += query ? `?${new URLSearchParams(query).toString()}` : ''; + + const response = await app.request(path); + const parsed = await parser.parseString(await response.text()); + const obj = { + description: parsed.description, + image: parsed.image, + items: parsed.items.slice(0, expectObj.items.length).map((e) => ({ + content: e.content, + enclosure: e.enclosure, + itunes: e.itunes, + })), + }; + expect(obj).toEqual(expectObj); + + return parsed; +}; + +const expectImgOrigin = async (query?: string | Record) => { + await testAntiHotlink('/test/complicated', expects.complicated.origin, query); + await testAntiHotlinkExtra('/test/complicated', expects.extraComplicated.origin, query); +}; +const expectImgProcessed = async (query?: string | Record) => { + await testAntiHotlink('/test/complicated', expects.complicated.processed, query); + await testAntiHotlinkExtra('/test/complicated', expects.extraComplicated.processed, query); +}; + +const expectImgUrlencoded = async (query?: string | Record) => { + await testAntiHotlink('/test/complicated', expects.complicated.urlencoded, query); + await testAntiHotlinkExtra('/test/complicated', expects.extraComplicated.urlencoded, query); +}; + +const expectMultimediaOrigin = async (query?: string | Record) => { + await testAntiHotlink('/test/multimedia', expects.multimedia.origin, query); + await testAntiHotlinkExtra('/test/multimedia', expects.extraMultimedia.origin, query); +}; + +const expectMultimediaRelayed = async (query?: string | Record) => { + await testAntiHotlink('/test/multimedia', expects.multimedia.relayed, query); + await testAntiHotlinkExtra('/test/multimedia', expects.extraMultimedia.relayed, query); +}; + +const expectMultimediaPartlyRelayed = async (query?: string | Record) => { + await testAntiHotlink('/test/multimedia', expects.multimedia.partlyRelayed, query); + await testAntiHotlinkExtra('/test/multimedia', expects.extraMultimedia.partlyRelayed, query); +}; + +describe('anti-hotlink', () => { + it('template-legacy', async () => { + process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}'; + await expectImgProcessed(); + }); + + it('template-experimental', async () => { + process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}'; + process.env.ALLOW_USER_HOTLINK_TEMPLATE = 'true'; + await expectImgProcessed(); + await expectMultimediaRelayed({ multimedia_hotlink_template: process.env.HOTLINK_TEMPLATE }); + }); + + it('url', async () => { + process.env.HOTLINK_TEMPLATE = '${protocol}//${host}${pathname}'; + await expectImgOrigin(); + await expectMultimediaOrigin({ multimedia_hotlink_template: process.env.HOTLINK_TEMPLATE }); + }); + + it('url-encoded', async () => { + process.env.HOTLINK_TEMPLATE = 'https://images.weserv.nl?url=${href_ue}'; + await expectImgUrlencoded(); + }); + + it('template-priority-legacy', async () => { + process.env.HOTLINK_TEMPLATE = '${protocol}//${host}${pathname}'; + await expectImgOrigin(); + }); + + it('template-priority-experimental', async () => { + process.env.ALLOW_USER_HOTLINK_TEMPLATE = 'true'; + await expectImgOrigin(); + await expectImgProcessed({ image_hotlink_template: 'https://i3.wp.com/${host}${pathname}' }); + }); + + it('no-template', async () => { + process.env.HOTLINK_TEMPLATE = ''; + await expectImgOrigin(); + await expectMultimediaOrigin(); + }); + + it('multimedia-template-experimental', async () => { + process.env.ALLOW_USER_HOTLINK_TEMPLATE = 'true'; + await expectMultimediaOrigin({ multimedia_hotlink_template: '${protocol}//${host}${pathname}' }); + await expectMultimediaPartlyRelayed({ multimedia_hotlink_template: 'https://i3.wp.com/${host}${pathname}' }); + }); + + it('include-paths-partial-matched', async () => { + process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}'; + process.env.HOTLINK_INCLUDE_PATHS = '/test'; + await expectImgProcessed(); + }); + + it('include-paths-fully-matched', async () => { + process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}'; + process.env.HOTLINK_INCLUDE_PATHS = '/test/complicated'; + await expectImgProcessed(); + }); + + it('include-paths-unmatched', async () => { + process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}'; + process.env.HOTLINK_INCLUDE_PATHS = '/t'; + await expectImgOrigin(); + }); + + it('exclude-paths-partial-matched', async () => { + process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}'; + process.env.HOTLINK_EXCLUDE_PATHS = '/test'; + await expectImgOrigin(); + }); + + it('exclude-paths-fully-matched', async () => { + process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}'; + process.env.HOTLINK_EXCLUDE_PATHS = '/test/complicated'; + await expectImgOrigin(); + }); + + it('exclude-paths-unmatched', async () => { + process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}'; + process.env.HOTLINK_EXCLUDE_PATHS = '/t'; + await expectImgProcessed(); + }); + + it('include-exclude-paths-mixed-filtered-out', async () => { + process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}'; + process.env.HOTLINK_INCLUDE_PATHS = '/test'; + process.env.HOTLINK_EXCLUDE_PATHS = '/test/complicated'; + await expectImgOrigin(); + }); + + it('include-exclude-paths-mixed-unfiltered-out', async () => { + process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${host}${pathname}'; + process.env.HOTLINK_INCLUDE_PATHS = '/test'; + process.env.HOTLINK_EXCLUDE_PATHS = '/test/c'; + await expectImgProcessed(); + }); + + it('invalid-property', async () => { + process.env.HOTLINK_TEMPLATE = 'https://i3.wp.com/${createObjectURL}'; + const app = (await import('@/app')).default; + const response = await app.request('/test/complicated'); + expect(await response.text()).toContain('Error: Invalid URL property: createObjectURL'); + }); +}); diff --git a/lib/middleware/anti-hotlink.ts b/lib/middleware/anti-hotlink.ts new file mode 100644 index 00000000000000..6b04cda304c769 --- /dev/null +++ b/lib/middleware/anti-hotlink.ts @@ -0,0 +1,168 @@ +import { config } from '@/config'; +import { load, type CheerioAPI } from 'cheerio'; +import logger from '@/utils/logger'; +import { type MiddlewareHandler } from 'hono'; +import { Data } from '@/types'; + +const templateRegex = /\${([^{}]+)}/g; +const allowedUrlProperties = new Set(['hash', 'host', 'hostname', 'href', 'origin', 'password', 'pathname', 'port', 'protocol', 'search', 'searchParams', 'username']); + +// match path or sub-path +const matchPath = (path: string, paths: string[]) => { + for (const p of paths) { + if (path.startsWith(p) && (path.length === p.length || path[p.length] === '/')) { + return true; + } + } + return false; +}; + +// return true if the path needs to be processed +const filterPath = (path: string) => { + const include = config.hotlink.includePaths; + const exclude = config.hotlink.excludePaths; + return !(include && !matchPath(path, include)) && !(exclude && matchPath(path, exclude)); +}; + +const interpolate = (str: string, obj: Record) => + str.replaceAll(templateRegex, (_, prop) => { + let needEncode = false; + if (prop.endsWith('_ue')) { + // url encode + prop = prop.slice(0, -3); + needEncode = true; + } + return needEncode ? encodeURIComponent(obj[prop]) : obj[prop]; + }); +const parseUrl = (str: string) => { + let url; + try { + url = new URL(str); + } catch { + logger.error(`Failed to parse ${str}`); + } + + return url; +}; + +const replaceUrl = (template?: string, url?: string) => { + if (!template || !url) { + return url; + } + const oldUrl = parseUrl(url); + if (oldUrl && oldUrl.protocol !== 'data:') { + return interpolate(template, oldUrl); + } + return url; +}; + +const replaceUrls = ($: CheerioAPI, selector: string, template: string, attribute = 'src') => { + $(selector).each(function () { + const oldSrc = $(this).attr(attribute); + if (oldSrc) { + const url = parseUrl(oldSrc); + if (url && url.protocol !== 'data:') { + // Cheerio will do the right thing to prohibit XSS. + $(this).attr(attribute, interpolate(template, url)); + } + } + }); +}; + +const process = (html: string, image_hotlink_template?: string, multimedia_hotlink_template?: string) => { + const $ = load(html, undefined, false); + if (image_hotlink_template) { + replaceUrls($, 'img, picture > source', image_hotlink_template); + replaceUrls($, 'video[poster]', image_hotlink_template, 'poster'); + replaceUrls($, '*[data-rsshub-image="href"]', image_hotlink_template, 'href'); + } + if (multimedia_hotlink_template) { + replaceUrls($, 'video, video > source, audio, audio > source', multimedia_hotlink_template); + if (!image_hotlink_template) { + replaceUrls($, 'video[poster]', multimedia_hotlink_template, 'poster'); + } + } + return $.html(); +}; + +const validateTemplate = (template?: string) => { + if (!template) { + return; + } + for (const match of template.matchAll(templateRegex)) { + const prop = match[1].endsWith('_ue') ? match[1].slice(0, -3) : match[1]; + if (!allowedUrlProperties.has(prop)) { + throw new Error(`Invalid URL property: ${prop}`); + } + } +}; + +const middleware: MiddlewareHandler = async (ctx, next) => { + await next(); + + let imageHotlinkTemplate: string | undefined; + let multimediaHotlinkTemplate: string | undefined; + + // Read params if enabled + if (config.feature.allow_user_hotlink_template) { + // By default, the config turns these features off. Set corresponding config to + // true to turn this feature on. + // A risk is that the media URLs will be replaced by user-supplied templates, + // so a user could literally take the control of "where are the media from", + // but only in their personal-use feed URL. + multimediaHotlinkTemplate = ctx.req.query('multimedia_hotlink_template'); + imageHotlinkTemplate = ctx.req.query('image_hotlink_template'); + } + + // Force config hotlink template on conflict + if (config.hotlink.template) { + imageHotlinkTemplate = filterPath(ctx.req.path) ? config.hotlink.template : undefined; + multimediaHotlinkTemplate = filterPath(ctx.req.path) ? config.hotlink.template : undefined; + } + + if (!imageHotlinkTemplate && !multimediaHotlinkTemplate) { + return; + } + + validateTemplate(imageHotlinkTemplate); + validateTemplate(multimediaHotlinkTemplate); + + // Assume that only description include image link + // and here we will only check them in description. + // Use Cheerio to load the description as html and filter all + // image link + const data: Data = ctx.get('data'); + if (data) { + if (data.image) { + data.image = replaceUrl(imageHotlinkTemplate, data.image); + } + if (data.description) { + data.description = process(data.description, imageHotlinkTemplate, multimediaHotlinkTemplate); + } + + if (data.item) { + for (const item of data.item) { + if (item.description) { + item.description = process(item.description, imageHotlinkTemplate, multimediaHotlinkTemplate); + } + if (item.enclosure_url && item.enclosure_type) { + if (item.enclosure_type.startsWith('image/')) { + item.enclosure_url = replaceUrl(imageHotlinkTemplate, item.enclosure_url); + } else if (/^(video|audio)\//.test(item.enclosure_type)) { + item.enclosure_url = replaceUrl(multimediaHotlinkTemplate, item.enclosure_url); + } + } + if (item.image) { + item.image = replaceUrl(imageHotlinkTemplate, item.image); + } + if (item.itunes_item_image) { + item.itunes_item_image = replaceUrl(imageHotlinkTemplate, item.itunes_item_image); + } + } + } + + ctx.set('data', data); + } +}; + +export default middleware; diff --git a/lib/middleware/api-response-handler.js b/lib/middleware/api-response-handler.js deleted file mode 100644 index e43954750c4c49..00000000000000 --- a/lib/middleware/api-response-handler.js +++ /dev/null @@ -1,147 +0,0 @@ -/** - * HTTP Status codes - */ -const statusCodes = { - CONTINUE: 100, - OK: 200, - CREATED: 201, - ACCEPTED: 202, - NO_CONTENT: 204, - BAD_REQUEST: 400, - UNAUTHORIZED: 401, - FORBIDDEN: 403, - NOT_FOUND: 404, - REQUEST_TIMEOUT: 408, - UNPROCESSABLE_ENTITY: 422, - INTERNAL_SERVER_ERROR: 500, - NOT_IMPLEMENTED: 501, - BAD_GATEWAY: 502, - SERVICE_UNAVAILABLE: 503, - GATEWAY_TIME_OUT: 504, -}; - -function responseHandler() { - return async (ctx, next) => { - ctx.res.statusCodes = statusCodes; - ctx.statusCodes = ctx.res.statusCodes; - - ctx.res.success = ({ statusCode, data = null, message = null }) => { - const status = 0; - - ctx.status = statusCode; - ctx.body = { status, data, message }; - }; - - // ctx.res.fail = ({ statusCode, code, data = null, message = null }) => { - // const status = -1; - - // if (!!statusCode && (statusCode >= 400 && statusCode < 500)) { - // ctx.status = statusCode; - // } else if (!(ctx.status >= 400 && ctx.status < 500)) { - // ctx.status = statusCodes.BAD_REQUEST; - // } - - // ctx.body = { status, code, data, message }; - // }; - - // ctx.res.error = ({ statusCode, code, data = null, message = null }) => { - // const status = -2; - - // if (!!statusCode && (statusCode >= 500 && statusCode < 600)) { - // ctx.status = statusCode; - // } else if (!(ctx.status >= 500 && ctx.status < 600)) { - // ctx.status = statusCodes.INTERNAL_SERVER_ERROR; - // } - - // ctx.body = { status, code, data, message }; - // }; - - ctx.res.ok = (params = {}) => { - ctx.res.success({ - ...params, - statusCode: statusCodes.OK, - }); - }; - - // ctx.res.noContent = (params = {}) => { - // ctx.res.success({ - // ...params, - // statusCode: statusCodes.NO_CONTENT, - // }); - // }; - - // ctx.res.badRequest = (params = {}) => { - // ctx.res.fail({ - // ...params, - // statusCode: statusCodes.BAD_REQUEST, - // }); - // }; - - // ctx.res.forbidden = (params = {}) => { - // ctx.res.fail({ - // ...params, - // statusCode: statusCodes.FORBIDDEN, - // }); - // }; - - // ctx.res.notFound = (params = {}) => { - // ctx.res.fail({ - // ...params, - // statusCode: statusCodes.NOT_FOUND, - // }); - // }; - - // ctx.res.requestTimeout = (params = {}) => { - // ctx.res.fail({ - // ...params, - // statusCode: statusCodes.REQUEST_TIMEOUT, - // }); - // }; - - // ctx.res.unprocessableEntity = (params = {}) => { - // ctx.res.fail({ - // ...params, - // statusCode: statusCodes.UNPROCESSABLE_ENTITY, - // }); - // }; - - // ctx.res.internalServerError = (params = {}) => { - // ctx.res.error({ - // ...params, - // statusCode: statusCodes.INTERNAL_SERVER_ERROR, - // }); - // }; - - // ctx.res.notImplemented = (params = {}) => { - // ctx.res.error({ - // ...params, - // statusCode: statusCodes.NOT_IMPLEMENTED, - // }); - // }; - - // ctx.res.badGateway = (params = {}) => { - // ctx.res.error({ - // ...params, - // statusCode: statusCodes.BAD_GATEWAY, - // }); - // }; - - // ctx.res.serviceUnavailable = (params = {}) => { - // ctx.res.error({ - // ...params, - // statusCode: statusCodes.SERVICE_UNAVAILABLE, - // }); - // }; - - // ctx.res.gatewayTimeOut = (params = {}) => { - // ctx.res.error({ - // ...params, - // statusCode: statusCodes.GATEWAY_TIME_OUT, - // }); - // }; - - await next(); - }; -} - -module.exports = responseHandler; diff --git a/lib/middleware/api-template.js b/lib/middleware/api-template.js deleted file mode 100644 index c3f10706db6708..00000000000000 --- a/lib/middleware/api-template.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = async (ctx, next) => { - await next(); - if (ctx.request.path.startsWith('/api/')) { - return ctx.res.ok({ - message: `request returned ${ctx.body.counter} ${ctx.body.counter > 1 ? 'routes' : 'route'}`, - data: ctx.body.result, - }); - } -}; diff --git a/lib/middleware/cache.test.ts b/lib/middleware/cache.test.ts new file mode 100644 index 00000000000000..703da060cd405e --- /dev/null +++ b/lib/middleware/cache.test.ts @@ -0,0 +1,189 @@ +import { describe, expect, it, vi, afterEach } from 'vitest'; +import Parser from 'rss-parser'; +import wait from '@/utils/wait'; + +process.env.CACHE_EXPIRE = '1'; +process.env.CACHE_CONTENT_EXPIRE = '2'; + +const parser = new Parser(); + +afterEach(() => { + vi.resetModules(); +}); + +const noCacheTestFunc = async () => { + const app = (await import('@/app')).default; + + const response1 = await app.request('/test/cache'); + const response2 = await app.request('/test/cache'); + + const parsed1 = await parser.parseString(await response1.text()); + const parsed2 = await parser.parseString(await response2.text()); + + expect(response2.status).toBe(200); + expect(response2.headers).not.toHaveProperty('rsshub-cache-status'); + + expect(parsed1.items[0].content).toBe('Cache1'); + expect(parsed2.items[0].content).toBe('Cache2'); + + expect(parsed1.ttl).toEqual('1'); +}; + +describe('cache', () => { + it('memory', async () => { + process.env.CACHE_TYPE = 'memory'; + const app = (await import('@/app')).default; + + const response1 = await app.request('/test/cache'); + const response2 = await app.request('/test/cache'); + + const parsed1 = await parser.parseString(await response1.text()); + const parsed2 = await parser.parseString(await response2.text()); + + delete parsed1.lastBuildDate; + delete parsed2.lastBuildDate; + delete parsed1.feedUrl; + delete parsed2.feedUrl; + delete parsed1.paginationLinks; + delete parsed2.paginationLinks; + expect(parsed2).toMatchObject(parsed1); + + expect(response2.status).toBe(200); + expect(response2.headers.get('rsshub-cache-status')).toBe('HIT'); + + expect(parsed1.ttl).toEqual('1'); + + await wait(1 * 1000 + 100); + const response3 = await app.request('/test/cache'); + expect(response3.headers).not.toHaveProperty('rsshub-cache-status'); + const parsed3 = await parser.parseString(await response3.text()); + + await wait(2 * 1000 + 100); + const response4 = await app.request('/test/cache'); + const parsed4 = await parser.parseString(await response4.text()); + + expect(parsed1.items[0].content).toBe('Cache1'); + expect(parsed2.items[0].content).toBe('Cache1'); + expect(parsed3.items[0].content).toBe('Cache1'); + expect(parsed4.items[0].content).toBe('Cache2'); + + await app.request('/test/refreshCache'); + await wait(1 * 1000 + 100); + const response5 = await app.request('/test/refreshCache'); + const parsed5 = await parser.parseString(await response5.text()); + await wait(1 * 1000 + 100); + const response6 = await app.request('/test/refreshCache'); + const parsed6 = await parser.parseString(await response6.text()); + + expect(parsed5.items[0].content).toBe('1 1'); + expect(parsed6.items[0].content).toBe('1 0'); + }, 10000); + + it('redis', async () => { + process.env.CACHE_TYPE = 'redis'; + const app = (await import('@/app')).default; + + await wait(500); + const response1 = await app.request('/test/cache'); + const response2 = await app.request('/test/cache'); + + const parsed1 = await parser.parseString(await response1.text()); + const parsed2 = await parser.parseString(await response2.text()); + + delete parsed1.lastBuildDate; + delete parsed2.lastBuildDate; + delete parsed1.feedUrl; + delete parsed2.feedUrl; + delete parsed1.paginationLinks; + delete parsed2.paginationLinks; + expect(parsed2).toMatchObject(parsed1); + + expect(response2.status).toBe(200); + expect(response2.headers.get('rsshub-cache-status')).toBe('HIT'); + + expect(parsed1.ttl).toEqual('1'); + + await wait(1 * 1000 + 100); + const response3 = await app.request('/test/cache'); + expect(response3.headers).not.toHaveProperty('rsshub-cache-status'); + const parsed3 = await parser.parseString(await response3.text()); + + await wait(2 * 1000 + 100); + const response4 = await app.request('/test/cache'); + const parsed4 = await parser.parseString(await response4.text()); + + expect(parsed1.items[0].content).toBe('Cache1'); + expect(parsed2.items[0].content).toBe('Cache1'); + expect(parsed3.items[0].content).toBe('Cache1'); + expect(parsed4.items[0].content).toBe('Cache2'); + + await app.request('/test/refreshCache'); + await wait(1 * 1000 + 100); + const response5 = await app.request('/test/refreshCache'); + const parsed5 = await parser.parseString(await response5.text()); + await wait(1 * 1000 + 100); + const response6 = await app.request('/test/refreshCache'); + const parsed6 = await parser.parseString(await response6.text()); + + expect(parsed5.items[0].content).toBe('1 1'); + expect(parsed6.items[0].content).toBe('1 0'); + + const cache = (await import('@/utils/cache')).default; + await cache.clients.redisClient!.quit(); + }, 10000); + + it('redis with quit', async () => { + process.env.CACHE_TYPE = 'redis'; + const cache = (await import('@/utils/cache')).default; + await cache.clients.redisClient!.quit(); + await noCacheTestFunc(); + }); + + it('redis with error', async () => { + process.env.CACHE_TYPE = 'redis'; + process.env.REDIS_URL = 'redis://wrongpath:6379'; + await noCacheTestFunc(); + const cache = (await import('@/utils/cache')).default; + await cache.clients.redisClient!.quit(); + }); + + it('no cache', async () => { + process.env.CACHE_TYPE = 'NO'; + await noCacheTestFunc(); + }); + + it('no cache (empty string)', async () => { + process.env.CACHE_TYPE = ''; + await noCacheTestFunc(); + }); + + it('throws URL key', async () => { + process.env.CACHE_TYPE = 'memory'; + const app = (await import('@/app')).default; + + try { + const response = await app.request('/test/cacheUrlKey'); + expect(response).toThrow(Error); + } catch (error: any) { + expect(error.message).toContain('Cache key must be a string'); + } + }); + + it('RSS TTL (no cache)', async () => { + process.env.CACHE_TYPE = ''; + process.env.CACHE_EXPIRE = '600'; + const app = (await import('@/app')).default; + const response = await app.request('/test/cache'); + const parsed = await parser.parseString(await response.text()); + expect(parsed.ttl).toEqual('1'); + }); + + it('RSS TTL (w/ cache)', async () => { + process.env.CACHE_TYPE = 'memory'; + process.env.CACHE_EXPIRE = '600'; + const app = (await import('@/app')).default; + const response = await app.request('/test/cache'); + const parsed = await parser.parseString(await response.text()); + expect(parsed.ttl).toEqual('10'); + }); +}); diff --git a/lib/middleware/cache.ts b/lib/middleware/cache.ts new file mode 100644 index 00000000000000..d31a26a5b0b13b --- /dev/null +++ b/lib/middleware/cache.ts @@ -0,0 +1,78 @@ +import xxhash from 'xxhash-wasm'; +import type { MiddlewareHandler } from 'hono'; + +import { config } from '@/config'; +import RequestInProgressError from '@/errors/types/request-in-progress'; +import cacheModule from '@/utils/cache/index'; +import { Data } from '@/types'; + +const bypassList = new Set(['/', '/robots.txt', '/logo.png', '/favicon.ico']); +// only give cache string, as the `!` condition tricky +// XXH64 is used to shrink key size +// plz, write these tips in comments! +const middleware: MiddlewareHandler = async (ctx, next) => { + if (!cacheModule.status.available || bypassList.has(ctx.req.path)) { + await next(); + return; + } + + const requestPath = ctx.req.path; + const limit = ctx.req.query('limit') ? `:${ctx.req.query('limit')}` : ''; + const { h64ToString } = await xxhash(); + const key = 'rsshub:koa-redis-cache:' + h64ToString(requestPath + limit); + const controlKey = 'rsshub:path-requested:' + h64ToString(requestPath + limit); + + const isRequesting = await cacheModule.globalCache.get(controlKey); + + if (isRequesting === '1') { + let retryTimes = process.env.NODE_ENV === 'test' ? 1 : 10; + let bypass = false; + while (retryTimes > 0) { + // eslint-disable-next-line no-await-in-loop + await new Promise((resolve) => setTimeout(resolve, process.env.NODE_ENV === 'test' ? 3000 : 6000)); + // eslint-disable-next-line no-await-in-loop + if ((await cacheModule.globalCache.get(controlKey)) !== '1') { + bypass = true; + break; + } + retryTimes--; + } + if (!bypass) { + throw new RequestInProgressError('This path is currently fetching, please come back later!'); + } + } + + const value = await cacheModule.globalCache.get(key); + + if (value) { + ctx.status(200); + ctx.header('RSSHub-Cache-Status', 'HIT'); + ctx.set('data', JSON.parse(value)); + await next(); + return; + } + + // Doesn't hit the cache? We need to let others know! + await cacheModule.globalCache.set(controlKey, '1', config.cache.requestTimeout); + + try { + await next(); + } catch (error) { + await cacheModule.globalCache.set(controlKey, '0', config.cache.requestTimeout); + throw error; + } + + const data: Data = ctx.get('data'); + if (ctx.res.headers.get('Cache-Control') !== 'no-cache' && data) { + data.lastBuildDate = new Date().toUTCString(); + ctx.set('data', data); + const body = JSON.stringify(data); + await cacheModule.globalCache.set(key, body, config.cache.routeExpire); + } + + // We need to let it go, even no cache set. + // Wait to set cache so the next request could be handled correctly + await cacheModule.globalCache.set(controlKey, '0', config.cache.requestTimeout); +}; + +export default middleware; diff --git a/lib/middleware/cache/index.js b/lib/middleware/cache/index.js deleted file mode 100644 index 52c0b3aeaaec0f..00000000000000 --- a/lib/middleware/cache/index.js +++ /dev/null @@ -1,138 +0,0 @@ -const md5 = require('@/utils/md5'); -const config = require('@/config').value; -const logger = require('@/utils/logger'); -const { RequestInProgressError } = require('@/errors'); - -const globalCache = { - get: () => null, - set: () => null, -}; - -let cacheModule = { - get: () => null, - set: () => null, - status: { available: false }, - clients: {}, -}; - -if (config.cache.type === 'redis') { - cacheModule = require('./redis'); - const { redisClient } = cacheModule.clients; - globalCache.get = async (key) => { - if (key && cacheModule.status.available) { - const value = await redisClient.get(key); - return value; - } - }; - globalCache.set = cacheModule.set; -} else if (config.cache.type === 'memory') { - cacheModule = require('./memory'); - const { memoryCache } = cacheModule.clients; - globalCache.get = (key) => { - if (key && cacheModule.status.available) { - return memoryCache.get(key, { updateAgeOnGet: false }); - } - }; - globalCache.set = (key, value, maxAge) => { - if (!value || value === 'undefined') { - value = ''; - } - if (typeof value === 'object') { - value = JSON.stringify(value); - } - if (key) { - return memoryCache.set(key, value, { ttl: maxAge * 1000 }); - } - }; -} else { - logger.error('Cache not available, concurrent requests are not limited. This could lead to bad behavior.'); -} - -// only give cache string, as the `!` condition tricky -// md5 is used to shrink key size -// plz, write these tips in comments! -module.exports = function (app) { - const { get, set, status } = cacheModule; - app.context.cache = { - ...cacheModule, - tryGet: async (key, getValueFunc, maxAge = config.cache.contentExpire, refresh = true) => { - if (typeof key !== 'string') { - throw Error('Cache key must be a string'); - } - let v = await get(key, refresh); - if (!v) { - v = await getValueFunc(); - set(key, v, maxAge); - } else { - let parsed; - try { - parsed = JSON.parse(v); - } catch (e) { - parsed = null; - } - if (parsed) { - v = parsed; - } - } - - return v; - }, - globalCache, - }; - - return async (ctx, next) => { - const key = 'koa-redis-cache:' + md5(ctx.request.path); - const controlKey = 'path-requested:' + md5(ctx.request.path); - - if (!status.available) { - return next(); - } - - const isRequesting = await globalCache.get(controlKey); - - if (isRequesting === '1') { - throw new RequestInProgressError('This path is currently fetching, please come back later!'); - } - - try { - const value = await globalCache.get(key); - - if (value) { - ctx.response.status = 200; - if (config.cache.type === 'redis') { - ctx.response.set({ - 'X-Koa-Redis-Cache': 'true', - }); - } else if (config.cache.type === 'memory') { - ctx.response.set({ - 'X-Koa-Memory-Cache': 'true', - }); - } - ctx.state.data = JSON.parse(value); - return; - } - } catch (e) { - // - } - - // Doesn't hit the cache? We need to let others know! - await globalCache.set(controlKey, '1', config.cache.requestTimeout); - - try { - await next(); - } catch (e) { - await globalCache.set(controlKey, '0', config.cache.requestTimeout); - throw e; - } - - if (ctx.response.get('Cache-Control') !== 'no-cache' && ctx.state && ctx.state.data) { - ctx.state.data.lastBuildDate = new Date().toUTCString(); - const body = JSON.stringify(ctx.state.data); - await globalCache.set(key, body, config.cache.routeExpire); - } - - // We need to let it go, even no cache set. - // Wait to set cache so the next request could be handled correctly - await globalCache.set(controlKey, '0', config.cache.requestTimeout); - }; -}; diff --git a/lib/middleware/cache/memory.js b/lib/middleware/cache/memory.js deleted file mode 100644 index 671ac3cf134675..00000000000000 --- a/lib/middleware/cache/memory.js +++ /dev/null @@ -1,36 +0,0 @@ -const Lru = require('lru-cache'); -const config = require('@/config').value; - -const status = { available: false }; - -const memoryCache = new Lru({ - ttl: config.cache.routeExpire * 1000, - max: config.memory.max, -}); - -status.available = true; - -module.exports = { - get: (key, refresh = true) => { - if (key && status.available) { - let value = memoryCache.get(key, { updateAgeOnGet: refresh }); - if (value) { - value = value + ''; - } - return value; - } - }, - set: (key, value, maxAge = config.cache.contentExpire) => { - if (!value || value === 'undefined') { - value = ''; - } - if (typeof value === 'object') { - value = JSON.stringify(value); - } - if (key && status.available) { - return memoryCache.set(key, value, { ttl: maxAge * 1000 }); - } - }, - clients: { memoryCache }, - status, -}; diff --git a/lib/middleware/cache/redis.js b/lib/middleware/cache/redis.js deleted file mode 100644 index 1e9889d0f6f7f9..00000000000000 --- a/lib/middleware/cache/redis.js +++ /dev/null @@ -1,68 +0,0 @@ -const config = require('@/config').value; -const Redis = require('ioredis'); -const logger = require('@/utils/logger'); - -const redisClient = new Redis(config.redis.url); - -const status = { available: false }; - -redisClient.on('error', (error) => { - status.available = false; - logger.error('Redis error: ', error); -}); -redisClient.on('end', () => { - status.available = false; -}); -redisClient.on('connect', () => { - status.available = true; - logger.info('Redis connected.'); -}); - -const getCacheTtlKey = (key) => { - if (key.startsWith('cacheTtl:')) { - throw Error('"cacheTtl:" prefix is reserved for the internal usage, please change your cache key'); // blocking any attempt to get/set the cacheTtl - } - return `cacheTtl:${key}`; -}; - -module.exports = { - get: async (key, refresh = true) => { - if (key && status.available) { - const cacheTtlKey = getCacheTtlKey(key); - let [value, cacheTtl] = await redisClient.mget(key, cacheTtlKey); - if (value && refresh) { - if (!cacheTtl) { - // if cacheTtl is not set, that means the cache expire time is contentExpire - cacheTtl = config.cache.contentExpire; - // dont save cacheTtl to Redis, as it is the default value - // redisClient.set(cacheTtlKey, cacheTtl, 'EX', cacheTtl); - } else { - redisClient.expire(cacheTtlKey, cacheTtl); - } - redisClient.expire(key, cacheTtl); - value = value + ''; - } - return value; - } - }, - set: (key, value, maxAge = config.cache.contentExpire) => { - if (!status.available) { - return; - } - if (!value || value === 'undefined') { - value = ''; - } - if (typeof value === 'object') { - value = JSON.stringify(value); - } - if (key) { - if (maxAge !== config.cache.contentExpire) { - // Only set cacheTtlKey if maxAge !== contentExpire - redisClient.set(getCacheTtlKey(key), maxAge, 'EX', maxAge); - } - return redisClient.set(key, value, 'EX', maxAge); // setMode: https://redis.io/commands/set - } - }, - clients: { redisClient }, - status, -}; diff --git a/lib/middleware/debug.js b/lib/middleware/debug.js deleted file mode 100644 index 644795418aae91..00000000000000 --- a/lib/middleware/debug.js +++ /dev/null @@ -1,25 +0,0 @@ -module.exports = async (ctx, next) => { - if (!ctx.debug.paths[ctx.request.path]) { - ctx.debug.paths[ctx.request.path] = 0; - } - ctx.debug.paths[ctx.request.path]++; - - ctx.debug.request++; - - await next(); - - if (!ctx.debug.routes[ctx._matchedRoute]) { - ctx._matchedRoute && (ctx.debug.routes[ctx._matchedRoute] = 0); - } - ctx._matchedRoute && ctx.debug.routes[ctx._matchedRoute]++; - - if (ctx.response.get('X-Koa-Redis-Cache') || ctx.response.get('X-Koa-Memory-Cache')) { - ctx.debug.hitCache++; - } - - ctx.state.debuged = true; - - if (ctx.status === 304) { - ctx.debug.etag++; - } -}; diff --git a/lib/middleware/debug.test.ts b/lib/middleware/debug.test.ts new file mode 100644 index 00000000000000..7bcf980e8c45cc --- /dev/null +++ b/lib/middleware/debug.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from 'vitest'; +import app from '@/app'; +import { load } from 'cheerio'; + +process.env.NODE_NAME = 'mock'; + +describe('debug', () => { + it('debug', async () => { + const response1 = await app.request('/test/1'); + const etag = response1.headers.get('etag'); + await app.request('/test/1', { + headers: { + 'If-None-Match': etag!, + }, + }); + await app.request('/test/2'); + await app.request('/test/empty'); + await app.request('/test/empty'); + + const response = await app.request('/'); + + const $ = load(await response.text()); + $('.debug-item').each((index, item) => { + const key = $(item).find('.debug-key').html()?.trim(); + const value = $(item).find('.debug-value').html()?.trim(); + switch (key) { + case 'Node Name:': + expect(value).toBe('mock'); + break; + case 'Request Amount:': + expect(value).toBe('6'); + break; + case 'ETag Matched:': + expect(value).toBe('1'); + break; + default: + } + }); + }); +}); diff --git a/lib/middleware/debug.ts b/lib/middleware/debug.ts new file mode 100644 index 00000000000000..c9fc8799f55997 --- /dev/null +++ b/lib/middleware/debug.ts @@ -0,0 +1,37 @@ +import { MiddlewareHandler } from 'hono'; +import { getDebugInfo, setDebugInfo } from '@/utils/debug-info'; + +const middleware: MiddlewareHandler = async (ctx, next) => { + { + const debug = getDebugInfo(); + if (!debug.paths[ctx.req.path]) { + debug.paths[ctx.req.path] = 0; + } + debug.paths[ctx.req.path]++; + + debug.request++; + setDebugInfo(debug); + } + + await next(); + + { + const debug = getDebugInfo(); + const hasMatchedRoute = ctx.req.routePath !== '/*'; + if (!debug.routes[ctx.req.routePath] && hasMatchedRoute) { + debug.routes[ctx.req.routePath] = 0; + } + hasMatchedRoute && debug.routes[ctx.req.routePath]++; + + if (ctx.res.headers.get('RSSHub-Cache-Status')) { + debug.hitCache++; + } + + if (ctx.res.status === 304) { + debug.etag++; + } + setDebugInfo(debug); + } +}; + +export default middleware; diff --git a/lib/middleware/filter-engine.test.ts b/lib/middleware/filter-engine.test.ts new file mode 100644 index 00000000000000..1a9d6e60b6a90c --- /dev/null +++ b/lib/middleware/filter-engine.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it, afterAll, vi, afterEach } from 'vitest'; + +afterAll(() => { + delete process.env.FILTER_REGEX_ENGINE; +}); + +afterEach(() => { + delete process.env.FILTER_REGEX_ENGINE; + vi.resetModules(); +}); + +describe('filter-engine', () => { + it(`filter RE2 engine ReDoS attack`, async () => { + const app = (await import('@/app')).default; + + const response = await app.request('/test/1?filter=abc(%3F%3Ddef)'); + expect(response.status).toBe(503); + expect(await response.text()).toMatch(/RE2JSSyntaxException/); + }); + + it(`filter Regexp engine backward compatibility`, async () => { + process.env.FILTER_REGEX_ENGINE = 'regexp'; + + const app = (await import('@/app')).default; + + const response = await app.request('/test/1?filter=abc(%3F%3Ddef)'); + expect(response.status).toBe(200); + }); + + it(`filter Regexp engine test config`, async () => { + process.env.FILTER_REGEX_ENGINE = 'somethingelse'; + + const app = (await import('@/app')).default; + + const response = await app.request('/test/1?filter=abc(%3F%3Ddef)'); + expect(response.status).toBe(503); + expect(await response.text()).toMatch(/somethingelse/); + }); +}); diff --git a/lib/middleware/header.js b/lib/middleware/header.js deleted file mode 100644 index 2292ddbed4eeac..00000000000000 --- a/lib/middleware/header.js +++ /dev/null @@ -1,45 +0,0 @@ -const etagCalculate = require('etag'); -const logger = require('@/utils/logger'); -const config = require('@/config').value; -const headers = { - 'Access-Control-Allow-Methods': 'GET', - 'Content-Type': 'application/xml; charset=utf-8', - 'Cache-Control': `public, max-age=${config.cache.routeExpire}`, - 'X-Content-Type-Options': 'nosniff', -}; -if (config.nodeName) { - headers['RSSHub-Node'] = config.nodeName; -} - -module.exports = async (ctx, next) => { - logger.info(`${ctx.url}, user IP: ${ctx.ips[0] || ctx.ip}`); - ctx.set(headers); - ctx.set({ - 'Access-Control-Allow-Origin': config.allowOrigin || ctx.host, - }); - - await next(); - - if (!ctx.body || typeof ctx.body !== 'string' || ctx.response.get('ETag')) { - return; - } - - const status = (ctx.status / 100) | 0; - if (2 !== status) { - return; - } - - ctx.set('ETag', etagCalculate(ctx.body.replace(/(.*)<\/lastBuildDate>/, '').replace(//, ''))); - - if (ctx.fresh) { - ctx.status = 304; - ctx.body = null; - } else { - const match = ctx.body.match(/(.*)<\/lastBuildDate>/); - if (match) { - ctx.set({ - 'Last-Modified': match[1], - }); - } - } -}; diff --git a/lib/middleware/header.test.ts b/lib/middleware/header.test.ts new file mode 100644 index 00000000000000..bda96a681f85c7 --- /dev/null +++ b/lib/middleware/header.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it, afterAll } from 'vitest'; + +process.env.NODE_NAME = 'mock'; +process.env.ALLOW_ORIGIN = 'rsshub.mock'; + +let etag; + +afterAll(() => { + delete process.env.NODE_NAME; + delete process.env.ALLOW_ORIGIN; +}); + +describe('header', () => { + it(`header`, async () => { + const app = (await import('@/app')).default; + const { config } = await import('@/config'); + const response = await app.request('/test/1'); + expect(response.headers.get('access-control-allow-origin')).toBe('rsshub.mock'); + expect(response.headers.get('access-control-allow-methods')).toBe('GET'); + expect(response.headers.get('content-type')).toBe('application/xml; charset=utf-8'); + expect(response.headers.get('cache-control')).toBe(`public, max-age=${config.cache.routeExpire}`); + expect(response.headers.get('last-modified')).toBe((await response.text()).match(/(.*)<\/lastBuildDate>/)?.[1]); + expect(response.headers.get('rsshub-node')).toBe('mock'); + expect(response.headers.get('etag')).not.toBe(undefined); + etag = response.headers.get('etag'); + }); + + it(`etag`, async () => { + const app = (await import('@/app')).default; + const response = await app.request('/test/1', { + headers: { + 'If-None-Match': etag, + }, + }); + expect(response.status).toBe(304); + expect(await response.text()).toBe(''); + expect(response.headers.get('last-modified')).toBe(null); + }); +}); diff --git a/lib/middleware/header.ts b/lib/middleware/header.ts new file mode 100644 index 00000000000000..f43d8614548f81 --- /dev/null +++ b/lib/middleware/header.ts @@ -0,0 +1,48 @@ +import { MiddlewareHandler } from 'hono'; +import etagCalculate from 'etag'; +import { config } from '@/config'; +import { Data } from '@/types'; + +const headers: Record = { + 'Access-Control-Allow-Methods': 'GET', + 'Content-Type': 'application/xml; charset=utf-8', + 'Cache-Control': `public, max-age=${config.cache.routeExpire}`, + 'X-Content-Type-Options': 'nosniff', +}; +if (config.nodeName) { + headers['RSSHub-Node'] = config.nodeName; +} + +function etagMatches(etag: string, ifNoneMatch: string | null) { + return ifNoneMatch !== null && ifNoneMatch.split(/,\s*/).includes(etag); +} + +const middleware: MiddlewareHandler = async (ctx, next) => { + for (const key in headers) { + ctx.header(key, headers[key]); + } + ctx.header('Access-Control-Allow-Origin', config.allowOrigin || new URL(ctx.req.url).host); + + await next(); + + const data: Data = ctx.get('data'); + if (!data || ctx.res.headers.get('ETag')) { + return; + } + + const lastBuildDate = data.lastBuildDate; + delete data.lastBuildDate; + const etag = etagCalculate(JSON.stringify(data)); + + ctx.header('ETag', etag); + + const ifNoneMatch = ctx.req.header('If-None-Match') ?? null; + if (etagMatches(etag, ifNoneMatch)) { + ctx.status(304); + ctx.set('no-content', true); + } else { + ctx.header('Last-Modified', lastBuildDate); + } +}; + +export default middleware; diff --git a/lib/middleware/load-on-demand.js b/lib/middleware/load-on-demand.js deleted file mode 100644 index 0c15bb42305437..00000000000000 --- a/lib/middleware/load-on-demand.js +++ /dev/null @@ -1,36 +0,0 @@ -const mount = require('koa-mount'); -const Router = require('@koa/router'); -const routes = require('../v2router'); -const loadedRoutes = new Set(); - -module.exports = function (app) { - return async function (ctx, next) { - const p = ctx.request.path.split('/').filter(Boolean); - let modName = null; - let mounted = false; - - if (p.length > 0) { - modName = p[0]; - if (!loadedRoutes.has(modName)) { - const mod = routes[modName]; - // Mount module - if (mod) { - mounted = true; - loadedRoutes.add(modName); - const router = new Router(); - mod(router); - app.use(mount(`/${modName}`, router.routes())).use(router.allowedMethods()); - } - } else { - mounted = true; - } - } - - await next(); - - // We should only add it when koa router matched - if (mounted && ctx._matchedRoute) { - ctx._matchedRoute = `/${modName}${ctx._matchedRoute}`; - } - }; -}; diff --git a/lib/middleware/logger.ts b/lib/middleware/logger.ts new file mode 100644 index 00000000000000..9d2ca59cec6b8f --- /dev/null +++ b/lib/middleware/logger.ts @@ -0,0 +1,44 @@ +import { requestMetric } from '@/utils/otel'; +import { MiddlewareHandler } from 'hono'; +import logger from '@/utils/logger'; +import { getPath, time } from '@/utils/helpers'; + +enum LogPrefix { + Outgoing = '-->', + Incoming = '<--', + Error = 'xxx', +} + +const colorStatus = (status: number) => { + const out: { [key: string]: string } = { + 7: `\u001B[35m${status}\u001B[0m`, + 5: `\u001B[31m${status}\u001B[0m`, + 4: `\u001B[33m${status}\u001B[0m`, + 3: `\u001B[36m${status}\u001B[0m`, + 2: `\u001B[32m${status}\u001B[0m`, + 1: `\u001B[32m${status}\u001B[0m`, + 0: `\u001B[33m${status}\u001B[0m`, + }; + + const calculateStatus = Math.trunc(status / 100); + + return out[calculateStatus]; +}; + +const middleware: MiddlewareHandler = async (ctx, next) => { + const { method, raw, routePath } = ctx.req; + const path = getPath(raw); + + logger.info(`${LogPrefix.Incoming} ${method} ${path}`); + + const start = Date.now(); + + await next(); + + const status = ctx.res.status; + + logger.info(`${LogPrefix.Outgoing} ${method} ${path} ${colorStatus(status)} ${time(start)}`); + requestMetric.success(Date.now() - start, { path: routePath, method, status }); +}; + +export default middleware; diff --git a/lib/middleware/onerror.js b/lib/middleware/onerror.js deleted file mode 100644 index 04c9c4c9f97836..00000000000000 --- a/lib/middleware/onerror.js +++ /dev/null @@ -1,113 +0,0 @@ -const logger = require('@/utils/logger'); -const config = require('@/config').value; -const art = require('art-template'); -const path = require('path'); - -const { RequestInProgressError } = require('@/errors'); - -let Sentry; -let gitHash; - -if (config.sentry.dsn) { - Sentry = Sentry || require('@sentry/node'); - Sentry.init({ - dsn: config.sentry.dsn, - }); - Sentry.configureScope((scope) => { - scope.setTag('node_name', config.nodeName); - }); - - logger.info('Sentry inited.'); -} - -try { - gitHash = require('git-rev-sync').short(); -} catch (e) { - gitHash = (process.env.HEROKU_SLUG_COMMIT && process.env.HEROKU_SLUG_COMMIT.slice(0, 7)) || (process.env.VERCEL_GIT_COMMIT_SHA && process.env.VERCEL_GIT_COMMIT_SHA.slice(0, 7)) || 'unknown'; -} - -module.exports = async (ctx, next) => { - try { - const time = +new Date(); - await next(); - if (config.sentry.dsn && +new Date() - time >= config.sentry.routeTimeout) { - Sentry.withScope((scope) => { - scope.setTag('route', ctx._matchedRoute); - scope.setTag('name', ctx.request.path.split('/')[1]); - scope.addEventProcessor((event) => Sentry.Handlers.parseRequest(event, ctx.request)); - Sentry.captureException(new Error('Route Timeout')); - }); - } - } catch (err) { - let message = err; - if (err.name && (err.name === 'HTTPError' || err.name === 'RequestError')) { - message = `${err.message}: target website might be blocking our access, you can host your own RSSHub instance for a better usability.`; - } else if (err instanceof Error) { - message = process.env.NODE_ENV === 'production' ? err.message : err.stack; - } - - logger.error(`Error in ${ctx.request.path}: ${message}`); - - if (config.isPackage) { - ctx.body = { - error: { - message: err.message ? err.message : err, - }, - }; - } else { - ctx.set({ - 'Content-Type': 'text/html; charset=UTF-8', - }); - - if (err instanceof RequestInProgressError) { - ctx.status = 503; - message = err.message; - ctx.set('Cache-Control', `public, max-age=${config.cache.requestTimeout}`); - } else if (ctx.status === 403) { - message = err.message; - } else { - ctx.status = 404; - } - - const requestPath = ctx.request.path; - - ctx.body = art(path.resolve(__dirname, '../views/error.art'), { - requestPath, - message, - errorPath: ctx.path, - nodeVersion: process.version, - gitHash, - }); - } - - if (!ctx.debug.errorPaths[ctx.request.path]) { - ctx.debug.errorPaths[ctx.request.path] = 0; - } - ctx.debug.errorPaths[ctx.request.path]++; - - if (!ctx.debug.errorRoutes[ctx._matchedRoute]) { - ctx._matchedRoute && (ctx.debug.errorRoutes[ctx._matchedRoute] = 0); - } - ctx._matchedRoute && ctx.debug.errorRoutes[ctx._matchedRoute]++; - - if (!ctx.state.debuged) { - if (!ctx.debug.routes[ctx._matchedRoute]) { - ctx._matchedRoute && (ctx.debug.routes[ctx._matchedRoute] = 0); - } - ctx._matchedRoute && ctx.debug.routes[ctx._matchedRoute]++; - - if (ctx.response.get('X-Koa-Redis-Cache') || ctx.response.get('X-Koa-Memory-Cache')) { - ctx.debug.hitCache++; - } - } - - if (config.sentry.dsn) { - Sentry.withScope((scope) => { - scope.setTag('route', ctx._matchedRoute); - scope.setTag('name', ctx.request.path.split('/')[1]); - scope.addEventProcessor((event) => Sentry.Handlers.parseRequest(event, ctx.request)); - Sentry.captureException(err); - }); - } - } -}; diff --git a/lib/middleware/parameter.js b/lib/middleware/parameter.js deleted file mode 100644 index 27153245270326..00000000000000 --- a/lib/middleware/parameter.js +++ /dev/null @@ -1,316 +0,0 @@ -const entities = require('entities'); -const cheerio = require('cheerio'); -const { simplecc } = require('simplecc-wasm'); -const got = require('@/utils/got'); -const config = require('@/config').value; -const RE2 = require('re2'); - -let mercury_parser; - -const resolveRelativeLink = ($, elem, attr, baseUrl) => { - const $elem = $(elem); - - if (baseUrl) { - try { - const oldAttr = $elem.attr(attr); - if (oldAttr) { - // e.g. should leave