diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 11a6d970d557..de89bc644fcc 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -2,7 +2,7 @@
// https://github.com/devcontainers/images/blob/v0.4.19/src/typescript-node/.devcontainer/devcontainer.json
{
"name": "Node.js & TypeScript",
- "image": "mcr.microsoft.com/devcontainers/typescript-node:24-bookworm",
+ "image": "mcr.microsoft.com/devcontainers/typescript-node:24-trixie",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker": {
"version": "latest"
@@ -40,7 +40,7 @@
}
},
- "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/*",
+ "onCreateCommand": "sudo apt-get update && export DEBIAN_FRONTEND=noninteractive && sudo apt-get -y install --no-install-recommends ca-certificates fonts-liberation libasound2t64 libatk-bridge2.0-0t64 libatk1.0-0t64 libatspi2.0-0t64 libcairo2 libcups2t64 libdbus-1-3 libexpat1 libgbm1 libglib2.0-0t64 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 && pnpx rebrowser-puppeteer browsers install chrome",
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 073aae6491d2..f80831c35b29 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -10,12 +10,15 @@ updates:
- dependencies
ignore:
# pin to version before it is sold to potential suspicious party
- # https://github.com/goofychris/art-template/issues/660 the issue created from original author stating v4.13.3
+ # https://github.com/goofychris/art-template/issues/660 the issue created from original author stating v4.13.3 (March, 2025)
# contains suspicious code and related issues (#658, #659) were deleted
# related:
# https://github.com/fastify/point-of-view/issues/463 https://github.com/fastify/point-of-view/pull/461#issuecomment-2718888986
# https://github.com/cnpm/bug-versions/pull/266 https://github.com/cnpm/cnpmcore/issues/777
# https://github.com/yoimiya-kokomi/Miao-Yunzai/pull/515 https://github.com/zhangfisher/flex-tools/commit/09b565dfe6e2932bb829613ddbe09f6d0acbccd4
+ # v4.13.5, v4.13.6 (May, 2026)
+ # https://web.archive.org/web/20260521024725/https://github.com/goofychris/art-template/issues/665
+ # https://safedep.io/art-template-npm-supply-chain-compromise/ https://github.com/ossf/malicious-packages/pull/1265
- dependency-name: art-template
versions: ['>=4.13.3']
groups:
diff --git a/.github/workflows/build-assets.yml b/.github/workflows/build-assets.yml
index 44e45a852264..5b03c411b6fd 100644
--- a/.github/workflows/build-assets.yml
+++ b/.github/workflows/build-assets.yml
@@ -18,9 +18,9 @@ jobs:
contents: write
steps:
- name: Checkout
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install pnpm
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
+ uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- name: Use Node.js Active LTS
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
@@ -51,7 +51,7 @@ jobs:
if: ${{ env.DOCS_API_TOKEN != '' }}
run: echo "defined=true" >> $GITHUB_OUTPUT
- name: Checkout docs
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
if: steps.check-docs-env.outputs.defined == 'true'
with:
repository: 'RSSNext/rsshub-docs'
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index a870789efa99..a0093b2d53a7 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -48,7 +48,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
# Initializes the CodeQL tools for scanning.
# TODO: use hash pinning when https://github.com/dependabot/dependabot-core/pull/13007 pass
diff --git a/.github/workflows/comment-on-issue.yml b/.github/workflows/comment-on-issue.yml
index 06d6825c0088..2b973468ca6d 100644
--- a/.github/workflows/comment-on-issue.yml
+++ b/.github/workflows/comment-on-issue.yml
@@ -26,8 +26,8 @@ jobs:
outputs:
closed: ${{ steps.check.outputs.closed }}
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: lts/*
@@ -60,8 +60,8 @@ jobs:
(needs.checkIssue.result == 'success' || needs.checkIssue.result == 'skipped') &&
needs.checkIssue.outputs.closed != 'true'
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: lts/*
diff --git a/.github/workflows/dependabot-fork.yml b/.github/workflows/dependabot-fork.yml
index 40494f4e0b40..3de657ec30d2 100644
--- a/.github/workflows/dependabot-fork.yml
+++ b/.github/workflows/dependabot-fork.yml
@@ -10,7 +10,7 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Comment Dependabot PR
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index 9c542fad47ad..29838d4b5900 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -61,7 +61,7 @@ jobs:
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Extract repository name
id: repo-name
@@ -277,7 +277,7 @@ jobs:
if: needs.check-env.outputs.check-docker == 'true'
timeout-minutes: 5
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0
diff --git a/.github/workflows/docker-test-cont.yml b/.github/workflows/docker-test-cont.yml
index 71de05f45bbe..66404d2b89a7 100644
--- a/.github/workflows/docker-test-cont.yml
+++ b/.github/workflows/docker-test-cont.yml
@@ -11,9 +11,10 @@ jobs:
runs-on: ubuntu-latest
permissions:
pull-requests: write
+ actions: read
if: ${{ github.event.workflow_run.conclusion == 'success' }} # skip if unsuccessful
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
# https://github.com/orgs/community/discussions/25220#discussioncomment-11316244
- name: Search the PR that triggered this workflow
@@ -42,7 +43,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
@@ -70,12 +71,12 @@ jobs:
- name: Fetch Docker image
if: (env.TEST_CONTINUE)
- uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
- workflow: ${{ github.event.workflow_run.workflow_id }}
- run_id: ${{ github.event.workflow_run.id }}
- name: docker-image
+ run-id: ${{ github.event.workflow_run.id }}
+ name: rsshub.tar.zst
path: ../artifacts-${{ github.run_id }}
+ github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Import Docker image and set up Docker container
if: (env.TEST_CONTINUE)
diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml
index 80e317c45364..6e7b68bd21a7 100644
--- a/.github/workflows/docker-test.yml
+++ b/.github/workflows/docker-test.yml
@@ -27,7 +27,7 @@ jobs:
timeout-minutes: 10
steps:
- name: Checkout
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Docker Buildx # needed by `cache-from`
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
@@ -75,6 +75,6 @@ jobs:
- name: Upload Docker image
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
- name: docker-image
+ archive: false
path: rsshub.tar.zst
retention-days: 1
diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml
index 776717f4fc1c..6f6d483e15cd 100644
--- a/.github/workflows/format.yml
+++ b/.github/workflows/format.yml
@@ -14,8 +14,8 @@ jobs:
timeout-minutes: 15
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: lts/*
diff --git a/.github/workflows/ghcr-retention.yml b/.github/workflows/ghcr-retention.yml
index ec98d38df410..eea0f5fc9258 100644
--- a/.github/workflows/ghcr-retention.yml
+++ b/.github/workflows/ghcr-retention.yml
@@ -13,7 +13,7 @@ jobs:
contents: read
steps:
- name: Delete old container versions (30+ days)
- uses: dataaxiom/ghcr-cleanup-action@f092b48ba3b604b2a83690dc4b2bbb3392e1045f # v1.2.1
+ uses: dataaxiom/ghcr-cleanup-action@d52806a0dc70b430571a37da1fde39733ffd640f # v1.2.2
with:
dry-run: false
token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/issue-command.yml b/.github/workflows/issue-command.yml
index 87e7edd967d4..cd4f7932a673 100644
--- a/.github/workflows/issue-command.yml
+++ b/.github/workflows/issue-command.yml
@@ -15,7 +15,7 @@ jobs:
pull-requests: write
steps:
- name: Checkout the latest code
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- name: Automatic Rebase
@@ -49,7 +49,7 @@ jobs:
group: vouch-manage
cancel-in-progress: false
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- id: vouch
uses: mitchellh/vouch/action/manage-by-issue@c6d80ead49839655b61b422700b7a3bc9d0804a9 # v1.4.2
@@ -102,16 +102,16 @@ jobs:
- name: Checkout
if: ${{ !github.event.issue.pull_request }}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Checkout PR
if: github.event.issue.pull_request
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ fromJson(steps.pr-data.outputs.data).head.ref }}
- name: Install pnpm
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
+ uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- name: Use Node.js Active LTS
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
@@ -120,7 +120,7 @@ jobs:
cache: 'pnpm'
- name: Install dependencies (pnpm)
- run: pnpm i && pnpm rb && pnpm exec playwright install chromium
+ run: pnpm i && pnpm rb && pnpm exec patchright install chromium
- name: Fetch affected routes
id: fetch-route
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index c269b1497144..4f3f64135197 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -20,8 +20,8 @@ jobs:
permissions:
security-events: write
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: lts/*
@@ -80,7 +80,7 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check if PR author is denounced
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml
index 5dcd201dc749..769e6593544c 100644
--- a/.github/workflows/npm-publish.yml
+++ b/.github/workflows/npm-publish.yml
@@ -22,8 +22,8 @@ jobs:
env:
HUSKY: 0
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: lts/*
diff --git a/.github/workflows/pr-review.yml b/.github/workflows/pr-review.yml
index 512b7b734d51..fae0adbafcf7 100644
--- a/.github/workflows/pr-review.yml
+++ b/.github/workflows/pr-review.yml
@@ -22,7 +22,7 @@ jobs:
pull-requests: write
steps:
- name: Checkout repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
# https://github.com/orgs/community/discussions/25220#discussioncomment-11316244
- name: Search the PR that triggered this workflow
@@ -104,6 +104,7 @@ jobs:
"grep*": "allow",
"head*": "allow",
"ls*": "allow",
+ "rg*": "allow",
"sed*": "allow",
"tail*": "allow",
"wc*": "allow"
diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml
index b7bda03ddc4f..083e4e78991e 100644
--- a/.github/workflows/semgrep.yml
+++ b/.github/workflows/semgrep.yml
@@ -22,7 +22,7 @@ jobs:
permissions:
security-events: write
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- run: semgrep ci --sarif > semgrep.sarif
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
diff --git a/.github/workflows/similar-issues.yml b/.github/workflows/similar-issues.yml
index 95372a91e63f..b25530e85cf1 100644
--- a/.github/workflows/similar-issues.yml
+++ b/.github/workflows/similar-issues.yml
@@ -18,7 +18,7 @@ jobs:
issues: write
steps:
- name: Checkout repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
diff --git a/.github/workflows/test-full-routes.yml b/.github/workflows/test-full-routes.yml
index bf306839795d..7d6d41c0332e 100644
--- a/.github/workflows/test-full-routes.yml
+++ b/.github/workflows/test-full-routes.yml
@@ -14,9 +14,9 @@ jobs:
contents: write
steps:
- name: Checkout
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install pnpm
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
+ uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- name: Use Node.js Active LTS
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index aedd27b554ed..a4c72db8e1dc 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -27,12 +27,11 @@ jobs:
strategy:
fail-fast: false
matrix:
- # playwright install hangs in node v26.1.0, https://github.com/microsoft/playwright/issues/40724
- node-version: [26.0.0, lts/*, lts/-1]
+ node-version: [latest, lts/*, lts/-1]
name: Vitest on Node ${{ matrix.node-version }}
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ matrix.node-version }}
@@ -40,7 +39,7 @@ jobs:
- name: Install dependencies (pnpm)
run: pnpm i
- name: Run postinstall script for dependencies
- run: pnpm rb && pnpm exec playwright install chromium
+ run: pnpm rb && pnpm exec patchright install chromium
- name: Build routes
run: pnpm build
- name: Build worker routes
@@ -53,7 +52,7 @@ jobs:
REDIS_URL: redis://localhost:${{ job.services.redis.ports['6379'] }}/
- name: Upload coverage to Codecov
if: ${{ matrix.node-version == 'lts/*' }}
- uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
+ uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos as documented, but seems broken
@@ -63,7 +62,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- node-version: [26.0.0, lts/*, lts/-1]
+ node-version: [latest, lts/*, lts/-1]
chromium:
- name: bundled Chromium
dependency: ''
@@ -76,8 +75,8 @@ jobs:
environment: '{ "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD": "1" }'
name: Vitest Playwright on Node ${{ matrix.node-version }} with ${{ matrix.chromium.name }}
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ matrix.node-version }}
@@ -91,12 +90,12 @@ jobs:
run: pnpm build
- name: Install bundled Chromium
if: ${{ matrix.chromium.dependency == '' }}
- run: pnpm exec playwright install chromium
+ run: pnpm exec patchright install chromium
- name: Install Chromium
if: ${{ matrix.chromium.dependency != '' }}
# '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,
+ # That's not really a problem since the Chromium-bundled Docker image is based on Debian trixie,
# which provides up-to-date native packages.
run: |
set -eux
@@ -125,8 +124,8 @@ jobs:
node-version: [26, 24, 22]
name: Build radar and maintainer on Node ${{ matrix.node-version }}
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ matrix.node-version }}
@@ -148,7 +147,7 @@ jobs:
pull-requests: write
contents: write
steps:
- - uses: fastify/github-action-merge-dependabot@30c3f8f14a4f7b315ba38dbc1b793d27128fef82 # v3.12.0
+ - uses: fastify/github-action-merge-dependabot@d1b52cc4c7e618b19de8a43c7138213b277e820c # v3.14.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
target: patch
diff --git a/.github/workflows/update-nix-hash.yml b/.github/workflows/update-nix-hash.yml
index 51f1f97812c6..8a400f38a727 100644
--- a/.github/workflows/update-nix-hash.yml
+++ b/.github/workflows/update-nix-hash.yml
@@ -18,7 +18,7 @@ jobs:
timeout-minutes: 10
steps:
- name: Checkout
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install Nix
uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
with:
diff --git a/.oxlintrc.json b/.oxlintrc.json
index 85967256d352..d9f0abc3a043 100644
--- a/.oxlintrc.json
+++ b/.oxlintrc.json
@@ -14,10 +14,10 @@
"jsPlugins": [
{ "name": "n", "specifier": "eslint-plugin-n" },
{ "name": "unicorn-js", "specifier": "eslint-plugin-unicorn" },
+ { "name": "regexp", "specifier": "eslint-plugin-regexp" },
"@stylistic/eslint-plugin",
"eslint-plugin-simple-import-sort",
- "oxlint-plugin-eslint",
- "./eslint-plugins/no-then.js",
+ { "name": "eslint-js", "specifier": "oxlint-plugin-eslint" },
"./eslint-plugins/nsfw-flag.js"
],
"rules": {
@@ -32,19 +32,19 @@
"no-const-assign": "error",
"no-constant-binary-expression": "error",
"no-constant-condition": "error",
- // "no-control-regex": "error", -> off
+ "no-control-regex": "error",
"no-debugger": "error",
"no-dupe-class-members": "error",
"no-dupe-else-if": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
- "no-empty-character-class": "error",
+ // "no-empty-character-class": "error", -> off, handled by eslint-plugin-regexp
"no-empty-pattern": "error",
"no-ex-assign": "error",
"no-fallthrough": "error",
"no-func-assign": "error",
"no-import-assign": "error",
- "no-invalid-regexp": "error",
+ // "no-invalid-regexp": "error", -> off, handled by eslint-plugin-regexp
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
@@ -65,7 +65,7 @@
"no-unsafe-optional-chaining": "error",
"no-unused-private-class-members": "error",
// "no-unused-vars": "error", -> off for @typescript-eslint/no-unused-vars
- "no-useless-backreference": "error",
+ // "no-useless-backreference": "error", -> off, handled by eslint-plugin-regexp
"use-isnan": "error",
"valid-typeof": "error",
// #endregion
@@ -148,45 +148,76 @@
// #endregion
// #region --- unicorn recommended ---
+ // "unicorn-js/better-dom-traversing": "error", false positive on cheerio
"unicorn/catch-error-name": "error",
+ "unicorn-js/class-reference-in-static-methods": "error",
+ // "unicorn-js/comment-content": "error", re-evaluate when unicorn > 66.0.0
"unicorn/consistent-assert": "error",
+ "unicorn-js/consistent-class-member-order": "error",
+ "unicorn-js/consistent-compound-words": "error",
"unicorn/consistent-date-clone": "error",
"unicorn/consistent-empty-array-spread": "error",
"unicorn/consistent-existence-index-check": "error",
- // "unicorn/consistent-function-scoping": "error",
+ "unicorn-js/consistent-export-decorator-position": "error",
+ // "unicorn/consistent-function-scoping": "error", -> warn
+ "unicorn-js/consistent-json-file-read": "error",
+ "unicorn-js/consistent-optional-chaining": "error",
"unicorn/consistent-template-literal-escape": "error",
"unicorn/empty-brace-spaces": "error",
"unicorn/error-message": "error",
"unicorn/escape-case": "error",
// "unicorn/expiring-todo-comments": "error", // not yet implemented
// "unicorn/explicit-length-check": "error",
+ "unicorn-js/explicit-timer-delay": "error",
// "unicorn/filename-case": "error",
"unicorn/import-style": "error",
- "unicorn-js/isolated-functions": "error", // use jsPlugins
+ "unicorn-js/isolated-functions": "error",
+ "unicorn-js/max-nested-calls": "error",
"unicorn/new-for-builtins": "error",
"unicorn/no-abusive-eslint-disable": "error",
"unicorn/no-accessor-recursion": "error",
"unicorn/no-anonymous-default-export": "error",
// "unicorn/no-array-callback-reference": "error",
+ "unicorn/no-array-fill-with-reference-type": "error",
"unicorn/no-array-for-each": "error",
+ "unicorn-js/no-array-from-fill": "error",
"unicorn/no-array-method-this-argument": "error",
// "unicorn/no-array-reduce": "error",
"unicorn/no-array-reverse": "error",
// "unicorn/no-array-sort": "error",
// "unicorn/no-await-expression-member": "error",
"unicorn/no-await-in-promise-methods": "error",
+ "unicorn-js/no-blob-to-file": "error",
+ // "unicorn-js/no-break-in-nested-loop": "error", unopinionated
+ // "unicorn-js/no-canvas-to-image": "error", // unused
+ "unicorn-js/no-computed-property-existence-check": "error",
+ "unicorn-js/no-confusing-array-splice": "error",
+ "unicorn-js/no-confusing-array-with": "error",
"unicorn/no-console-spaces": "error",
+ // "unicorn-js/no-declarations-before-early-exit": "error", re-evaluate when unicorn > 66.0.0
"unicorn/no-document-cookie": "error",
+ "unicorn-js/no-duplicate-loops": "error",
+ "unicorn-js/no-duplicate-set-values": "error",
// "unicorn/no-empty-file": "error",
+ "unicorn-js/no-error-property-assignment": "error",
+ "unicorn-js/no-exports-in-scripts": "error",
// "unicorn/no-for-loop": "error", // won't be implemented
+ "unicorn-js/no-global-object-property-assignment": "error",
// "unicorn/no-hex-escape": "error",
"unicorn/no-immediate-mutation": "error",
+ "unicorn-js/no-incorrect-query-selector": "error",
+ // "unicorn-js/no-incorrect-template-string-interpolation": "error", too many false positives
"unicorn/no-instanceof-builtins": "error",
+ "unicorn-js/no-invalid-argument-count": "error",
"unicorn/no-invalid-fetch-options": "error",
"unicorn/no-invalid-remove-event-listener": "error",
+ "unicorn-js/no-late-current-target-access": "error",
"unicorn/no-lonely-if": "error",
"unicorn/no-magic-array-flat-depth": "error",
+ "unicorn-js/no-mismatched-map-key": "error",
// "unicorn/no-named-default": "error", -> use import/no-named-default
+ "unicorn-js/no-negated-array-predicate": "error",
+ "unicorn-js/no-negated-comparison": "error",
"no-negated-condition": "off",
"unicorn/no-negated-condition": "error",
"unicorn/no-negation-in-equality-check": "error",
@@ -196,38 +227,64 @@
"unicorn/no-new-buffer": "error",
// "unicorn/no-null": "error",
// "unicorn/no-object-as-default-parameter": "error",
+ "unicorn-js/no-object-methods-with-collections": "error",
+ "unicorn-js/no-optional-chaining-on-undeclared-variable": "error",
// "unicorn/no-process-exit": "error",
+ "unicorn-js/no-redundant-comparison": "error",
+ "unicorn-js/no-return-array-push": "error",
"unicorn/no-single-promise-in-promise-methods": "error",
"unicorn/no-static-only-class": "error",
+ "unicorn-js/no-subtraction-comparison": "error",
"unicorn/no-thenable": "error",
"unicorn/no-this-assignment": "error",
+ "unicorn-js/no-this-outside-of-class": "error",
+ // "unicorn-js/no-top-level-side-effects": "error", // allows dayjs.extend()
"unicorn/no-typeof-undefined": "error",
+ "unicorn-js/no-undeclared-class-members": "error",
"unicorn/no-unnecessary-array-flat-depth": "error",
"unicorn/no-unnecessary-array-splice-count": "error",
"unicorn/no-unnecessary-await": "error",
- "unicorn-js/no-unnecessary-polyfills": "error", // use jsPlugins
+ "unicorn-js/no-unnecessary-global-this": "error",
+ "unicorn-js/no-unnecessary-nested-ternary": "error",
+ "unicorn-js/no-unnecessary-polyfills": "error",
"unicorn/no-unnecessary-slice-end": "error",
+ "unicorn-js/no-unnecessary-splice": "error",
"unicorn/no-unreadable-array-destructuring": "error",
"unicorn/no-unreadable-iife": "error",
+ // "unicorn-js/no-unreadable-new-expression": "error", // allows new URL(x).href
+ "unicorn-js/no-unreadable-object-destructuring": "error",
+ "unicorn-js/no-unsafe-buffer-conversion": "error",
+ "unicorn-js/no-unsafe-property-key": "error",
+ "unicorn-js/no-unsafe-string-replacement": "error",
+ "unicorn-js/no-unused-array-method-return": "error",
+ "unicorn-js/no-useless-boolean-cast": "error",
"unicorn/no-useless-collection-argument": "error",
+ "unicorn-js/no-useless-concat": "error",
+ "unicorn-js/no-useless-else": "error",
"unicorn/no-useless-error-capture-stack-trace": "error",
"unicorn/no-useless-fallback-in-spread": "error",
// "unicorn/no-useless-iterator-to-array": "error",
"unicorn/no-useless-length-check": "error",
"unicorn/no-useless-promise-resolve-reject": "error",
+ // "unicorn-js/no-useless-recursion": "error", unopinionated
"unicorn/no-useless-spread": "error",
// "unicorn/no-useless-switch-case": "error",
+ "unicorn-js/no-useless-template-literals": "error",
// "unicorn/no-useless-undefined": "error",
"unicorn/no-zero-fractions": "error",
// "unicorn/number-literal-case": "error",
// "unicorn/numeric-separators-style": "error",
"unicorn/prefer-add-event-listener": "error",
+ "unicorn-js/prefer-add-event-listener-options": "error",
"unicorn/prefer-array-find": "error",
"unicorn/prefer-array-flat": "error",
"unicorn/prefer-array-flat-map": "error",
+ "unicorn-js/prefer-array-from-map": "error",
"unicorn/prefer-array-index-of": "error",
+ "unicorn-js/prefer-array-last-methods": "error",
"unicorn/prefer-array-some": "error",
"unicorn/prefer-at": "error",
+ "unicorn-js/prefer-await": "error",
"unicorn/prefer-bigint-literals": "error",
"unicorn/prefer-blob-reading-methods": "error",
"unicorn/prefer-class-fields": "error",
@@ -235,66 +292,166 @@
// "unicorn/prefer-code-point": "error",
"unicorn/prefer-date-now": "error",
"unicorn/prefer-default-parameters": "error",
+ "unicorn-js/prefer-direct-iteration": "error",
"unicorn/prefer-dom-node-append": "error",
"unicorn/prefer-dom-node-dataset": "error",
"unicorn/prefer-dom-node-remove": "error",
"unicorn/prefer-dom-node-text-content": "error",
+ "unicorn-js/prefer-early-return": "error",
"unicorn/prefer-event-target": "error",
- "unicorn-js/prefer-export-from": "error", // use jsPlugins
+ "unicorn/prefer-export-from": "error",
+ "unicorn-js/prefer-get-or-insert-computed": "error",
+ "unicorn-js/prefer-global-number-constants": "error",
// "unicorn/prefer-global-this": "error",
+ // "unicorn-js/prefer-https": "error",
+ "unicorn-js/prefer-identifier-import-export-specifiers": "error",
"unicorn/prefer-includes": "error",
+ "unicorn-js/prefer-includes-over-repeated-comparisons": "error",
+ "unicorn-js/prefer-iterable-in-constructor": "error",
+ "unicorn-js/prefer-iterator-to-array": "error",
+ // "unicorn-js/prefer-iterator-to-array-at-end": "error",
"unicorn/prefer-keyboard-event-key": "error",
+ "unicorn-js/prefer-location-assign": "error",
"unicorn/prefer-logical-operator-over-ternary": "error",
+ "unicorn-js/prefer-math-abs": "error",
"unicorn/prefer-math-min-max": "error",
"unicorn/prefer-math-trunc": "error",
+ // "unicorn-js/prefer-minimal-ternary": "error", re-evaluate when unicorn > 66.0.0
"unicorn/prefer-modern-dom-apis": "error",
"unicorn/prefer-modern-math-apis": "error",
// "unicorn/prefer-module": "error",
"unicorn/prefer-native-coercion-functions": "error",
"unicorn/prefer-negative-index": "error",
"unicorn/prefer-node-protocol": "error",
- // "unicorn/prefer-number-properties": "error",
+ "unicorn-js/prefer-number-coercion": "error",
+ "unicorn-js/prefer-number-is-safe-integer": "error",
+ // "unicorn/prefer-number-properties": "error", // checkNaN: false
+ "unicorn-js/prefer-object-define-properties": "error",
+ "unicorn-js/prefer-object-destructuring-defaults": "error",
"unicorn/prefer-object-from-entries": "error",
+ "unicorn-js/prefer-object-iterable-methods": "error",
"unicorn/prefer-optional-catch-binding": "error",
+ // "unicorn-js/prefer-path2d": "error", // unused
+ "unicorn-js/prefer-private-class-fields": "error",
"unicorn/prefer-prototype-methods": "error",
"unicorn/prefer-query-selector": "error",
+ "unicorn-js/prefer-queue-microtask": "error",
"unicorn/prefer-reflect-apply": "error",
"unicorn/prefer-regexp-test": "error",
"unicorn/prefer-response-static-json": "error",
+ "unicorn-js/prefer-scoped-selector": "error",
"unicorn/prefer-set-has": "error",
"unicorn/prefer-set-size": "error",
- "unicorn-js/prefer-simple-condition-first": "error", // use jsPlugins
- "unicorn-js/prefer-single-call": "error", // use jsPlugins
+ "unicorn-js/prefer-short-arrow-method": "error",
+ "unicorn-js/prefer-simple-condition-first": "error",
+ "unicorn-js/prefer-simple-sort-comparator": "error",
+ "unicorn-js/prefer-single-array-predicate": "error",
+ "unicorn/prefer-single-call": "error",
+ "unicorn-js/prefer-single-object-destructuring": "error",
+ "unicorn-js/prefer-smaller-scope": "error",
+ "unicorn-js/prefer-split-limit": "error",
// "unicorn/prefer-spread": "error",
+ "unicorn-js/prefer-string-match-all": "error",
+ "unicorn-js/prefer-string-pad-start-end": "error",
"unicorn/prefer-string-raw": "error",
+ "unicorn-js/prefer-string-repeat": "error",
"unicorn/prefer-string-replace-all": "error",
// "unicorn/prefer-string-slice": "error",
- "unicorn/prefer-string-starts-ends-with": "error",
+ // "unicorn/prefer-string-starts-ends-with": "error", use typescript/prefer-string-starts-ends-with
"unicorn/prefer-string-trim-start-end": "error",
"unicorn/prefer-structured-clone": "error",
- // "unicorn/prefer-switch": "error", // use jsPlugins
+ // "unicorn/prefer-switch": "error",
"unicorn/prefer-ternary": "error",
// "unicorn/prefer-top-level-await": "error",
"unicorn/prefer-type-error": "error",
+ "unicorn-js/prefer-type-literal-last": "error",
+ // "unicorn-js/prefer-uint8array-base64": "error", re-evaluate when node > 25
+ "unicorn-js/prefer-url-href": "error",
// "unicorn/prevent-abbreviations": "error",
"unicorn/relative-url-style": "error",
"unicorn/require-array-join-separator": "error",
+ "unicorn-js/require-array-sort-compare": "error",
+ "unicorn-js/require-css-escape": "error",
"unicorn/require-module-attributes": "error",
"unicorn/require-module-specifiers": "error",
"unicorn/require-number-to-fixed-digits-argument": "error",
+ "unicorn-js/require-passive-events": "error",
+ "unicorn-js/require-proxy-trap-boolean-return": "error",
// "unicorn/switch-case-braces": "error",
- "unicorn-js/switch-case-break-position": "error", // use jsPlugins
- "unicorn-js/template-indent": "error", // use jsPlugins
+ "unicorn-js/switch-case-break-position": "error",
+ "unicorn-js/template-indent": "error",
// "unicorn/text-encoding-identifier-case": "error",
"unicorn/throw-new-error": "error",
// #endregion
+ // #region --- regexp recommended ---
+ "regexp/confusing-quantifier": "warn",
+ "regexp/control-character-escape": "error",
+ "regexp/match-any": "error",
+ "regexp/negation": "error",
+ "regexp/no-contradiction-with-assertion": "error",
+ "regexp/no-dupe-characters-character-class": "error",
+ "regexp/no-dupe-disjunctions": "error",
+ "regexp/no-empty-alternative": "warn",
+ "regexp/no-empty-capturing-group": "error",
+ "regexp/no-empty-character-class": "error",
+ "regexp/no-empty-group": "error",
+ "regexp/no-empty-lookarounds-assertion": "error",
+ "regexp/no-empty-string-literal": "error",
+ "regexp/no-escape-backspace": "error",
+ "regexp/no-extra-lookaround-assertions": "error",
+ "regexp/no-invalid-regexp": "error",
+ "regexp/no-invisible-character": "error",
+ "regexp/no-lazy-ends": "warn",
+ "regexp/no-legacy-features": "error",
+ "regexp/no-misleading-capturing-group": "error",
+ "regexp/no-misleading-unicode-character": "error",
+ "regexp/no-missing-g-flag": "error",
+ "regexp/no-non-standard-flag": "error",
+ "regexp/no-obscure-range": "error",
+ "regexp/no-optional-assertion": "error",
+ "regexp/no-potentially-useless-backreference": "warn",
+ "regexp/no-super-linear-backtracking": "error",
+ "regexp/no-trivially-nested-assertion": "error",
+ "regexp/no-trivially-nested-quantifier": "error",
+ "regexp/no-unused-capturing-group": "error",
+ "regexp/no-useless-assertions": "error",
+ "regexp/no-useless-backreference": "error",
+ "regexp/no-useless-character-class": "error",
+ "regexp/no-useless-dollar-replacements": "error",
+ "regexp/no-useless-escape": "error",
+ "regexp/no-useless-flag": "warn",
+ "regexp/no-useless-lazy": "error",
+ "regexp/no-useless-non-capturing-group": "error",
+ "regexp/no-useless-quantifier": "error",
+ "regexp/no-useless-range": "error",
+ "regexp/no-useless-set-operand": "error",
+ "regexp/no-useless-string-literal": "error",
+ "regexp/no-useless-two-nums-quantifier": "error",
+ "regexp/no-zero-quantifier": "error",
+ "regexp/optimal-lookaround-quantifier": "warn",
+ "regexp/optimal-quantifier-concatenation": "error",
+ "regexp/prefer-character-class": "error",
+ "regexp/prefer-d": "error",
+ "regexp/prefer-plus-quantifier": "error",
+ "regexp/prefer-predefined-assertion": "error",
+ "regexp/prefer-question-quantifier": "error",
+ "regexp/prefer-range": "error",
+ "regexp/prefer-set-operation": "error",
+ "regexp/prefer-star-quantifier": "error",
+ "regexp/prefer-unicode-codepoint-escapes": "error",
+ "regexp/prefer-w": "error",
+ "regexp/simplify-set-operations": "error",
+ "regexp/sort-flags": "error",
+ "regexp/strict": "error",
+ "regexp/use-ignore-case": "error",
+ // #endregion
+
// --- custom rules ---
// #region --- possible problems ---
"array-callback-return": ["error", { "allowImplicit": true }],
"no-await-in-loop": "error",
- "no-control-regex": "off",
"no-prototype-builtins": "off",
"no-undef": "off", // typescript/eslint-recommended, ts(2552)
// #endregion
@@ -324,14 +481,14 @@
}
],
- "eslint-js/no-implicit-globals": "error", // use jsPlugins
+ "eslint-js/no-implicit-globals": "error",
"no-labels": "error",
"no-lonely-if": "error",
"no-multi-str": "error",
"no-new-func": "error",
"eslint-js/no-restricted-syntax": [
- "error", // use jsPlugins
+ "error",
{
"selector": "CallExpression[callee.property.name='get'][arguments.length=0]",
"message": "Please use .toArray() instead."
@@ -383,7 +540,7 @@
"no-useless-concat": "warn",
"no-useless-rename": "error",
"no-var": "error",
- "eslint-js/object-shorthand": "error", // use jsPlugins
+ "eslint-js/object-shorthand": "error",
"prefer-arrow-callback": "error",
"prefer-const": "error",
"prefer-object-has-own": "error",
@@ -413,6 +570,8 @@
"@typescript-eslint/no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true }],
"@typescript-eslint/no-unused-vars": ["error", { "args": "after-used", "argsIgnorePattern": "^_" }],
+ "@typescript-eslint/prefer-string-starts-ends-with": "error", // type-aware
+
// type-aware
// "@typescript-eslint/await-thenable": "off",
// "@typescript-eslint/no-base-to-string": "off",
@@ -559,9 +718,6 @@
],
// #endregion
- // github
- "github/no-then": "warn",
-
// rsshub
"@rsshub/nsfw-flag/add-nsfw-flag": "error"
},
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 6c0244f8d43f..6e9b14134c6b 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,84 +1,83 @@
-# Contributor Covenant Code of Conduct
+# Contributor Covenant 3.0 Code of Conduct
## Our Pledge
-We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
+We pledge to make our community welcoming, safe, and equitable for all.
-We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
+We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or religion, national or social origin, socio-economic position, level of education, or other status. The same privileges of participation are extended to everyone who participates in good faith and in accordance with this Covenant.
-## Our Standards
+## Encouraged Behaviors
-Examples of behavior that contributes to a positive environment for our community include:
+While acknowledging differences in social norms, we all strive to meet our community's expectations for positive behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture, background, or native language.
-- Demonstrating empathy and kindness toward other people
-- Being respectful of differing opinions, viewpoints, and experiences
-- Giving and gracefully accepting constructive feedback
-- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
-- Focusing on what is best not just for us as individuals, but for the overall community
+With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared values, including:
-Examples of unacceptable behavior include:
+1. Respecting the **purpose of our community**, our activities, and our ways of gathering.
+2. Engaging **kindly and honestly** with others.
+3. Respecting **different viewpoints** and experiences.
+4. **Taking responsibility** for our actions and contributions.
+5. Gracefully giving and accepting **constructive feedback**.
+6. Committing to **repairing harm** when it occurs.
+7. Behaving in other ways that promote and sustain the **well-being of our community**.
-- The use of sexualized language or imagery, and sexual attention or
- advances of any kind
-- Trolling, insulting or derogatory comments, and personal or political attacks
-- Public or private harassment
-- Publishing others' private information, such as a physical or email
- address, without their explicit permission
-- Other conduct which could reasonably be considered inappropriate in a
- professional setting
+## Restricted Behaviors
-## Enforcement Responsibilities
+We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are violations of this Code of Conduct.
-Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
+1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any clear request to stop.
+2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of people.
+3. **Stereotyping or discrimination.** Characterizing anyone’s personality or behavior on the basis of immutable identities or traits.
+4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or purpose of the community.
+5. **Violating confidentiality**. Sharing or acting on someone's personal or private information without their permission.
+6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group.
+7. Behaving in other ways that **threaten the well-being** of our community.
-Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
+### Other Restrictions
-## Scope
-
-This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
-
-## Enforcement
-
-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.
-
-## Enforcement Guidelines
+1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade enforcement actions.
+2. **Failing to credit sources.** Not properly crediting the sources of content you contribute.
+3. **Promotional materials**. Sharing marketing or other commercial content in a way that is outside the norms of the community.
+4. **Irresponsible communication.** Failing to responsibly present content which includes, links or describes any other restricted behaviors.
-Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
+## Reporting an Issue
-### 1. Correction
+Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviors and norms that can help avoid conflicts and minimize harm.
-**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
+When an incident does occur, it is important to report it promptly. To report a possible violation, send an email to .
-**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
+Community Moderators take reports of violations seriously and will make every effort to respond in a timely manner. They will investigate all reports of code of conduct violations, reviewing messages, logs, and recordings, or interviewing witnesses and other participants. Community Moderators will keep investigation and enforcement actions as transparent as possible while prioritizing safety and confidentiality. In order to honor these values, enforcement actions are carried out in private with the involved parties, but communicating to the whole community may be part of a mutually agreed upon resolution.
-### 2. Warning
+## Addressing and Repairing Harm
-**Community Impact**: A violation through a single incident or series of actions.
+If an investigation by the Community Moderators finds that this Code of Conduct has been violated, the following enforcement ladder may be used to determine how best to repair harm, based on the incident's impact on the individuals involved and the community as a whole. Depending on the severity of a violation, lower rungs on the ladder may be skipped.
-**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
+1. Warning
+ 1. Event: A violation involving a single incident or series of incidents.
+ 2. Consequence: A private, written warning from the Community Moderators.
+ 3. Repair: Examples of repair include a private written apology, acknowledgement of responsibility, and seeking clarification on expectations.
+2. Temporarily Limited Activities
+ 1. Event: A repeated incidence of a violation that previously resulted in a warning, or the first incidence of a more serious violation.
+ 2. Consequence: A private, written warning with a time-limited cooldown period designed to underscore the seriousness of the situation and give the community members involved time to process the incident. The cooldown period may be limited to particular communication channels or interactions with particular community members.
+ 3. Repair: Examples of repair may include making an apology, using the cooldown period to reflect on actions and impact, and being thoughtful about re-entering community spaces after the period is over.
+3. Temporary Suspension
+ 1. Event: A pattern of repeated violation which the Community Moderators have tried to address with warnings, or a single serious violation.
+ 2. Consequence: A private written warning with conditions for return from suspension. In general, temporary suspensions give the person being suspended time to reflect upon their behavior and possible corrective actions.
+ 3. Repair: Examples of repair include respecting the spirit of the suspension, meeting the specified conditions for return, and being thoughtful about how to reintegrate with the community when the suspension is lifted.
+4. Permanent Ban
+ 1. Event: A pattern of repeated code of conduct violations that other steps on the ladder have failed to resolve, or a violation so serious that the Community Moderators determine there is no way to keep the community safe with this person as a member.
+ 2. Consequence: Access to all community spaces, tools, and communication channels is removed. In general, permanent bans should be rarely used, should have strong reasoning behind them, and should only be resorted to if working through other remedies has failed to change the behavior.
+ 3. Repair: There is no possible repair in cases of this severity.
-### 3. Temporary Ban
+This enforcement ladder is intended as a guideline. It does not limit the ability of Community Managers to use their discretion and judgment, in keeping with the best interests of our community.
-**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
-
-**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
-
-### 4. Permanent Ban
-
-**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
+## Scope
-**Consequence**: A permanent ban from any sort of public interaction within the project community.
+This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public or other spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Attribution
-This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
-available at ${text.slice(0, brief)}… ${text}
')[0] ?? '', { allowedTags: [] }),
+ title: thread.sub ?? sanitizeHtml(thread.com?.split('
', 1)[0] ?? '', { allowedTags: [] }),
}));
};
diff --git a/lib/routes/4khd/category.ts b/lib/routes/4khd/category.ts
index 34d93d5d856d..85e9d5f0e87c 100644
--- a/lib/routes/4khd/category.ts
+++ b/lib/routes/4khd/category.ts
@@ -37,9 +37,8 @@ async function handler(ctx) {
const categoryUrl = `${SUB_URL}pages/${category}/`;
const slug = category === 'album' ? 'photo' : category;
- const {
- data: [{ id: categoryId }],
- } = await got(`${SUB_URL}wp-json/wp/v2/categories?slug=${slug}`);
+ const { data } = await got(`${SUB_URL}wp-json/wp/v2/categories?slug=${slug}`);
+ const categoryId = data[0].id;
const { data: posts } = await got(`${SUB_URL}wp-json/wp/v2/posts?categories=${categoryId}&per_page=${limit}`);
return {
diff --git a/lib/routes/4ksj/forum.tsx b/lib/routes/4ksj/forum.tsx
index 4762c3e87bb8..91c10204a7dd 100644
--- a/lib/routes/4ksj/forum.tsx
+++ b/lib/routes/4ksj/forum.tsx
@@ -114,7 +114,7 @@ async function handler(ctx) {
const scriptUrl = new URL(scriptPath, rootUrl).href;
const scriptResponse = await ofetch(scriptUrl);
- const key = scriptResponse.match(/{var key="(.*?)"/)?.[1];
+ const key = scriptResponse.match(/\{var key="(.*?)"/)?.[1];
const value = scriptResponse.match(/",value="(.*?)"/)?.[1];
const getPath = scriptResponse.match(/\.get\("(.*?&key=)"/)?.[1];
@@ -125,7 +125,7 @@ async function handler(ctx) {
const cookieResponse = await ofetch.raw(`${rootUrl}${getPath}${key}&value=${md5(stringtoHex(value))}`);
return cookieResponse.headers
.getSetCookie()
- .map((c) => c.split(';')[0])
+ .map((c) => c.split(';', 1)[0])
.join('; ');
});
diff --git a/lib/routes/4kup/category.ts b/lib/routes/4kup/category.ts
index f4e2a64a23d7..fb34cf712e25 100644
--- a/lib/routes/4kup/category.ts
+++ b/lib/routes/4kup/category.ts
@@ -36,9 +36,8 @@ async function handler(ctx) {
const category = ctx.req.param('category');
const categoryUrl = `${SUB_URL}category/${category}/`;
- const {
- data: [{ id: categoryId }],
- } = await got(`${SUB_URL}wp-json/wp/v2/categories?slug=${category}`);
+ const { data } = await got(`${SUB_URL}wp-json/wp/v2/categories?slug=${category}`);
+ const categoryId = data[0].id;
const { data: posts } = await got(`${SUB_URL}wp-json/wp/v2/posts?categories=${categoryId}&per_page=${limit}`);
return {
diff --git a/lib/routes/4kup/popular.ts b/lib/routes/4kup/popular.ts
index 4f1e710180fd..f431c9034f9e 100644
--- a/lib/routes/4kup/popular.ts
+++ b/lib/routes/4kup/popular.ts
@@ -40,7 +40,8 @@ function getPeriodConfig(period) {
range: 'last7days',
title: `${SUB_NAME_PREFIX} - Top views in 7 days`,
};
- } else if (period === '30') {
+ }
+ if (period === '30') {
return {
url: `${SUB_URL}hot-of-month/`,
range: 'last30days',
diff --git a/lib/routes/4kup/tag.ts b/lib/routes/4kup/tag.ts
index 023dca2de270..e97841ca9054 100644
--- a/lib/routes/4kup/tag.ts
+++ b/lib/routes/4kup/tag.ts
@@ -36,9 +36,8 @@ async function handler(ctx) {
const tag = ctx.req.param('tag');
const tagUrl = `${SUB_URL}tag/${tag}/`;
- const {
- data: [{ id: tagId }],
- } = await got(`${SUB_URL}wp-json/wp/v2/tags?slug=${tag}`);
+ const { data } = await got(`${SUB_URL}wp-json/wp/v2/tags?slug=${tag}`);
+ const tagId = data[0].id;
const { data: posts } = await got(`${SUB_URL}wp-json/wp/v2/posts?tags=${tagId}&per_page=${limit}`);
return {
diff --git a/lib/routes/51cto/recommend.ts b/lib/routes/51cto/recommend.ts
index 1a5c21871351..27f78306c5b7 100644
--- a/lib/routes/51cto/recommend.ts
+++ b/lib/routes/51cto/recommend.ts
@@ -41,7 +41,7 @@ async function getFullcontent(item, cookie = '') {
try {
// More details: https://github.com/DIYgod/RSSHub/pull/16583#discussion_r1738643033
const _matches = articleResponse!.match(pattern)!.slice(0, 3);
- const matches = _matches.map((str) => Number(str.split(':')[1]));
+ const matches = _matches.map((str) => Number(str.split(':', 2)[1]));
const [v1, v2, v3] = matches;
const cookie = '__tst_status=' + (v1 + v2 + v3) + '#;';
return await getFullcontent(item, cookie);
@@ -65,7 +65,7 @@ async function handler(ctx) {
const timestamp = Date.now();
const params = {
page: 1,
- page_size: ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 50,
+ page_size: ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 50,
limit_time: 0,
name_en: '',
};
diff --git a/lib/routes/51cto/utils.ts b/lib/routes/51cto/utils.ts
index 912612053b67..59566b87d8af 100644
--- a/lib/routes/51cto/utils.ts
+++ b/lib/routes/51cto/utils.ts
@@ -16,6 +16,6 @@ export const getToken = () =>
export const sign = (requestPath: string, payload: Record
))}
diff --git a/lib/routes/69shu/article.ts b/lib/routes/69shu/article.ts
index bc13b6c30ce7..c38d92ded4a8 100644
--- a/lib/routes/69shu/article.ts
+++ b/lib/routes/69shu/article.ts
@@ -54,8 +54,8 @@ const createItem = (url: string) =>
cache.tryGet(url, async () => {
const html = await get(url);
const $ = load(html);
- const { articleid, chapterid, chaptername } = parseObject(/bookinfo\s?=\s?{[\S\s]+?}/, $('head>script:not([src])').text());
- const decryptionMap = parseObject(/_\d+\s?=\s?{[\S\s]+?}/, $('.txtnav+script').text());
+ const { articleid, chapterid, chaptername } = parseObject(/bookinfo\s?=\s?\{[\s\S]+?\}/, $('head>script:not([src])').text());
+ const decryptionMap = parseObject(/_\d+\s?=\s?\{[\s\S]+?\}/, $('.txtnav+script').text());
return {
title: chaptername,
@@ -70,7 +70,7 @@ const parseObject = (reg: RegExp, str: string): Record{detail.label}
- {detail.value?.href && detail.value?.text ? {detail.value.text} : detail.value}
+ {detail.value?.href && detail.value.text ? {detail.value.text} : detail.value}
')
- .flatMap((line, index, array) => (lineMap[index] ? array[lineMap[index]] : line).split('
'))
+ .flatMap((line, index, array) => {
+ const mapped = lineMap[index];
+ return (mapped ? array[mapped] : line).split('
');
+ })
.slice(1, -2)
.join('
');
};
diff --git a/lib/routes/6park/index.ts b/lib/routes/6park/index.ts
index 4e2015f42835..e3904f8f9342 100644
--- a/lib/routes/6park/index.ts
+++ b/lib/routes/6park/index.ts
@@ -66,7 +66,7 @@ async function handler(ctx) {
const content = load(detailResponse.data);
item.title = content('title').text().replace(' -6park.com', '');
- item.author = detailResponse.data.match(/送交者: .*>(.*)<.*\[/)[1];
+ item.author = detailResponse.data.match(/送交者:[^>]*>([^<]*)<\/a>/)[1].trim();
item.pubDate = timezone(parseDate(detailResponse.data.match(/于 (.*) 已读/)[1], 'YYYY-MM-DD h:m'), +8);
item.description = content('pre')
.html()
diff --git a/lib/routes/6park/news.ts b/lib/routes/6park/news.ts
index 2050185e5d8e..30941bde8440 100644
--- a/lib/routes/6park/news.ts
+++ b/lib/routes/6park/news.ts
@@ -76,7 +76,7 @@ async function handler(ctx) {
const content = load(detailResponse.data);
- const matches = detailResponse.data.match(/新闻来源:(.*?)于.*(\d{4}(?:-\d{2}){2} (?:\d{1,2}:){2}\d{1,2})/);
+ const matches = detailResponse.data.match(/新闻来源:([^于]*)于.*(\d{4}(?:-\d{2}){2} (?:\d{1,2}:){2}\d{1,2})/);
item.title = content('h2').text();
item.author = matches[1].trim();
diff --git a/lib/routes/6v123/index.ts b/lib/routes/6v123/index.ts
index be6dfa62c9aa..8da5bd463402 100644
--- a/lib/routes/6v123/index.ts
+++ b/lib/routes/6v123/index.ts
@@ -12,7 +12,7 @@ import { parseDate } from '@/utils/parse-date';
export const handler = async (ctx: Context): Promise => {
const { category = 'dy' } = ctx.req.param();
- const limit: number = Number.parseInt(ctx.req.query('limit') ?? '25', 10);
+ const limit = Number(ctx.req.query('limit') ?? '25');
const encoding = 'gb2312';
diff --git a/lib/routes/78dm/index.ts b/lib/routes/78dm/index.ts
index 7a7be12bbae0..1fb95b2dea3c 100644
--- a/lib/routes/78dm/index.ts
+++ b/lib/routes/78dm/index.ts
@@ -10,7 +10,7 @@ import { renderDescription } from './templates/description';
export const handler = async (ctx) => {
const { category = 'news' } = ctx.req.param();
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 10;
const rootUrl = 'https://www.78dm.net';
const currentUrl = new URL(category.includes('/') ? `${category}.html` : category, rootUrl).href;
diff --git a/lib/routes/81/81rc/index.ts b/lib/routes/81/81rc/index.ts
index e3687a4e0708..b9f6b974bbd8 100644
--- a/lib/routes/81/81rc/index.ts
+++ b/lib/routes/81/81rc/index.ts
@@ -8,7 +8,7 @@ import timezone from '@/utils/timezone';
export const handler = async (ctx) => {
const { category = 'sy/gzdt_210283' } = ctx.req.param();
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
const rootUrl = 'https://81rc.81.cn';
const currentUrl = new URL(category?.endsWith('/') ? `${category}/` : category, rootUrl).href;
diff --git a/lib/routes/8264/list.tsx b/lib/routes/8264/list.tsx
index adcae9585e46..1ff23156917a 100644
--- a/lib/routes/8264/list.tsx
+++ b/lib/routes/8264/list.tsx
@@ -86,7 +86,7 @@ export const route: Route = {
async function handler(ctx) {
const { id = '751' } = ctx.req.param();
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
const rootUrl = 'https://www.8264.com';
const currentUrl = new URL(`list/${id}`, rootUrl).href;
diff --git a/lib/routes/8kcos/cat.ts b/lib/routes/8kcos/cat.ts
index f758aa1e5bbe..38195cb38da5 100644
--- a/lib/routes/8kcos/cat.ts
+++ b/lib/routes/8kcos/cat.ts
@@ -24,7 +24,7 @@ export const route: Route = {
};
async function handler(ctx) {
- const limit = Number.parseInt(ctx.req.query('limit') ?? 10, 10);
+ const limit = Number(ctx.req.query('limit') ?? 10);
const { cat = '8kasianidol' } = ctx.req.param();
const categoryInfo = await getCategoryInfo(cat);
const items = await getPosts(limit, { categories: categoryInfo.id });
diff --git a/lib/routes/8kcos/latest.ts b/lib/routes/8kcos/latest.ts
index 3fb94126373c..5113cb041224 100644
--- a/lib/routes/8kcos/latest.ts
+++ b/lib/routes/8kcos/latest.ts
@@ -28,7 +28,7 @@ export const route: Route = {
};
async function handler(ctx) {
- const limit = Number.parseInt(ctx.req.query('limit') ?? 10, 10);
+ const limit = Number(ctx.req.query('limit') ?? 10);
const items = await getPosts(limit);
return {
title: `${SUB_NAME_PREFIX}-最新`,
diff --git a/lib/routes/8kcos/tag.ts b/lib/routes/8kcos/tag.ts
index 3ffbad1fb878..928904912cb4 100644
--- a/lib/routes/8kcos/tag.ts
+++ b/lib/routes/8kcos/tag.ts
@@ -28,13 +28,13 @@ export const route: Route = {
};
async function handler(ctx) {
- const limit = Number.parseInt(ctx.req.query('limit') ?? 10, 10);
+ const limit = Number(ctx.req.query('limit') ?? 10);
const tag = ctx.req.param('tag');
const tagInfo = await getTagInfo(tag);
const items = await getPosts(limit, { tags: tagInfo.id });
return {
- title: `${tagInfo.title}`,
+ title: tagInfo.title,
link: `${SUB_URL}/tag/${tag}/`,
item: items,
};
diff --git a/lib/routes/95mm/utils.tsx b/lib/routes/95mm/utils.tsx
index 49cb2562f344..be22b86a9c62 100644
--- a/lib/routes/95mm/utils.tsx
+++ b/lib/routes/95mm/utils.tsx
@@ -44,7 +44,7 @@ const ProcessItems = async (ctx, title, currentUrl) => {
item.description = renderToString(
<>
{images.map((image) => (
-
+
))}
>
);
diff --git a/lib/routes/9to5/subsite.ts b/lib/routes/9to5/subsite.ts
index 07b65cc641a7..2695c7559934 100644
--- a/lib/routes/9to5/subsite.ts
+++ b/lib/routes/9to5/subsite.ts
@@ -49,8 +49,10 @@ async function handler(ctx) {
const feed = await parser.parseURL(link);
+ const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10;
+
const items = await Promise.all(
- feed.items.splice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10).map((item) =>
+ feed.items.splice(0, limit).map((item) =>
cache.tryGet(item.link, async () => {
const response = await got({
method: 'get',
diff --git a/lib/routes/9to5/utils.ts b/lib/routes/9to5/utils.ts
index 3cbb329b1fc7..3a3542b1ea05 100644
--- a/lib/routes/9to5/utils.ts
+++ b/lib/routes/9to5/utils.ts
@@ -20,11 +20,12 @@ const ProcessFeed = (data) => {
content.find('div.ad-disclaimer-container').remove();
content.find('div').each((i, e) => {
- if ($(e)[0].attribs.class) {
- const classes = $(e)[0].attribs.class;
- if (/\w{10}\s\w{10}/g.test(classes)) {
- $(e).remove();
- }
+ if (!$(e)[0].attribs.class) {
+ return;
+ }
+ const classes = $(e)[0].attribs.class;
+ if (/\w{10}\s\w{10}/.test(classes)) {
+ $(e).remove();
}
});
diff --git a/lib/routes/a9vg/index.ts b/lib/routes/a9vg/index.ts
index 2cbda0659faa..a38a324555ad 100644
--- a/lib/routes/a9vg/index.ts
+++ b/lib/routes/a9vg/index.ts
@@ -10,7 +10,7 @@ import { renderDescription } from './templates/description';
export const handler = async (ctx) => {
const { category = 'news/All' } = ctx.req.param();
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 15;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 15;
const rootUrl = 'http://www.a9vg.com';
const currentUrl = new URL(`list/${category}`, rootUrl).href;
diff --git a/lib/routes/aa1/60s.ts b/lib/routes/aa1/60s.ts
index a6569c4b5906..b125d7e71a65 100644
--- a/lib/routes/aa1/60s.ts
+++ b/lib/routes/aa1/60s.ts
@@ -9,7 +9,7 @@ import { parseDate } from '@/utils/parse-date';
export const handler = async (ctx: Context): Promise => {
const { category } = ctx.req.param();
- const limit: number = Number.parseInt(ctx.req.query('limit') ?? '100', 10);
+ const limit = Number(ctx.req.query('limit') ?? '100');
const apiSlug = 'wp-json/wp/v2';
const baseUrl = 'https://60s.aa1.cn';
diff --git a/lib/routes/abc/index.ts b/lib/routes/abc/index.ts
index a4eea519884e..7c1c969a9402 100644
--- a/lib/routes/abc/index.ts
+++ b/lib/routes/abc/index.ts
@@ -9,7 +9,7 @@ import { renderDescription } from './templates/description';
export const route: Route = {
path: '/:category{.+}?',
- example: '/wa',
+ example: '/abc/wa',
radar: [
{
source: ['abc.net.au/:category*'],
@@ -34,7 +34,7 @@ The supported channels are all listed in the table below. For other channels, pl
async function handler(ctx) {
const { category = 'news/justin' } = ctx.req.param();
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
const rootUrl = 'https://www.abc.net.au';
const apiUrl = new URL('news-web/api/loader/channelrefetch', rootUrl).href;
@@ -49,7 +49,7 @@ async function handler(ctx) {
const feedUrl = new URL(`news/feed/${documentId}/rss.xml`, rootUrl).href;
const feedResponse = await ofetch(feedUrl);
- currentUrl = feedResponse.match(/([\w-./:?]+)<\/link>/)[1];
+ currentUrl = feedResponse.match(/([\w./:?-]+)<\/link>/)[1];
}
const currentResponse = await ofetch(currentUrl);
@@ -73,7 +73,7 @@ async function handler(ctx) {
description: renderDescription({
image: i.image
? {
- src: i.image.imgSrc.split(/\?/)[0],
+ src: i.image.imgSrc.split(/\?/, 1)[0],
alt: i.image.alt,
}
: undefined,
@@ -86,7 +86,7 @@ async function handler(ctx) {
if (i.mediaIndicator) {
item.enclosure_type = 'audio/mpeg';
- item.itunes_item_image = i.image?.imgSrc.split(/\?/)[0] ?? undefined;
+ item.itunes_item_image = i.image?.imgSrc.split(/\?/, 1)[0] ?? undefined;
item.itunes_duration = i.mediaIndicator.duration;
}
@@ -111,7 +111,7 @@ async function handler(ctx) {
element.replaceWith(
renderDescription({
image: {
- src: element.find('img').prop('src').split(/\?/)[0],
+ src: element.find('img').prop('src').split(/\?/, 1)[0],
alt: element.find('figcaption').text().trim(),
},
})
@@ -124,14 +124,14 @@ async function handler(ctx) {
item.title = content('meta[property="og:title"]').prop('content');
item.description = '';
- const enclosurePattern = String.raw`"(?:MIME|content)?Type":"([\w]+/[\w]+)".*?"(?:fileS|s)?ize":(\d+),.*?"url":"([\w-.:/?]+)"`;
+ const enclosurePattern = String.raw`"(?:MIME|content)?Type":"(\w+/\w+)".*?"(?:fileS|s)?ize":(\d+),.*?"url":"([\w.:/?-]+)"`;
const enclosureMatches = detailResponse.match(new RegExp(enclosurePattern, 'g'));
if (enclosureMatches) {
const enclosureMatch = enclosureMatches
.map((e) => e.match(new RegExp(enclosurePattern)))
- .toSorted((a, b) => Number.parseInt(a[2], 10) - Number.parseInt(b[2], 10))
+ .toSorted((a, b) => Number(a[2]) - Number(b[2]))
.pop();
item.enclosure_url = enclosureMatch[3];
@@ -179,7 +179,7 @@ async function handler(ctx) {
link: currentUrl,
description: $('meta[property="og:description"]').prop('content'),
language: $('html').prop('lang'),
- image: $('meta[property="og:image"]').prop('content').split('?')[0],
+ image: $('meta[property="og:image"]').prop('content').split('?', 1)[0],
icon,
logo: icon,
subtitle: $('meta[property="og:title"]').prop('content'),
diff --git a/lib/routes/abc/templates/description.tsx b/lib/routes/abc/templates/description.tsx
index 334ec2e93c36..bd407bfaa126 100644
--- a/lib/routes/abc/templates/description.tsx
+++ b/lib/routes/abc/templates/description.tsx
@@ -15,7 +15,7 @@ type DescriptionData = {
};
const AbcDescription = ({ image, enclosure, description }: DescriptionData) => {
- const enclosureTag = enclosure?.type?.split('/')[0] as keyof JSX.IntrinsicElements | undefined;
+ const enclosureTag = enclosure?.type?.split('/', 1)[0] as keyof JSX.IntrinsicElements | undefined;
return (
<>
diff --git a/lib/routes/abmedia/category.ts b/lib/routes/abmedia/category.ts
index 4594821ce4b1..a99ea2b70f6b 100644
--- a/lib/routes/abmedia/category.ts
+++ b/lib/routes/abmedia/category.ts
@@ -6,7 +6,10 @@ const rootUrl = 'https://www.abmedia.io';
const cateAPIUrl = `${rootUrl}/wp-json/wp/v2/categories`;
const postsAPIUrl = `${rootUrl}/wp-json/wp/v2/posts`;
-const getCategoryId = (category) => got.get(`${cateAPIUrl}?slug=${category}`).then((res) => res.data[0].id);
+const getCategoryId = async (category) => {
+ const res = await got.get(`${cateAPIUrl}?slug=${category}`);
+ return res.data[0].id;
+};
export const route: Route = {
path: '/:category?',
diff --git a/lib/routes/accessbriefing/index.ts b/lib/routes/accessbriefing/index.ts
index 9ea022076b30..05bd4a039f42 100644
--- a/lib/routes/accessbriefing/index.ts
+++ b/lib/routes/accessbriefing/index.ts
@@ -9,7 +9,7 @@ import { renderDescription } from './templates/description';
export const handler = async (ctx) => {
const { category = 'latest/news' } = ctx.req.param();
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
const rootUrl = 'https://www.accessbriefing.com';
const currentUrl = new URL(category, rootUrl).href;
diff --git a/lib/routes/acfun/article.ts b/lib/routes/acfun/article.ts
index fef9499e500a..ef3f183f6df2 100644
--- a/lib/routes/acfun/article.ts
+++ b/lib/routes/acfun/article.ts
@@ -45,7 +45,7 @@ export const route: Route = {
parameters: {
categoryId: {
description: '分区 ID',
- options: Object.keys(categoryMap).map((id) => ({ value: id, label: categoryMap[id].title })),
+ options: Object.entries(categoryMap).map(([id, value]) => ({ value: id, label: value.title })),
},
sortType: {
description: '排序',
@@ -94,7 +94,7 @@ export const route: Route = {
async function handler(ctx) {
const { categoryId, sortType = 'createTime', timeRange = 'all' } = ctx.req.param();
- if (!categoryMap[categoryId]) {
+ if (!Object.hasOwn(categoryMap, categoryId)) {
throw new InvalidParameterError(`Invalid category Id: ${categoryId}`);
}
if (!sortTypeEnum.has(sortType)) {
diff --git a/lib/routes/acfun/bangumi.ts b/lib/routes/acfun/bangumi.ts
index 01be818d10df..d29c818e959f 100644
--- a/lib/routes/acfun/bangumi.ts
+++ b/lib/routes/acfun/bangumi.ts
@@ -44,7 +44,7 @@ async function handler(ctx) {
image: bangumiData.belongResource.coverImageV,
item: bangumiList.items.map((item) => ({
title: `${item.episodeName}${item.title ? ` - ${item.title}` : ''}`,
- description: renderDescription({ embed, aid: `ac${item.itemId}`, img: item.imgInfo.thumbnailImage.cdnUrls[0].url.split('?')[0] }),
+ description: renderDescription({ embed, aid: `ac${item.itemId}`, img: item.imgInfo.thumbnailImage.cdnUrls[0].url.split('?', 1)[0] }),
link: `https://www.acfun.cn/bangumi/aa${id}_36188_${item.itemId}`,
pubDate: parseDate(item.updateTime, 'x'),
})),
diff --git a/lib/routes/acfun/video.ts b/lib/routes/acfun/video.ts
index 39ca0200be73..365e6ebc227e 100644
--- a/lib/routes/acfun/video.ts
+++ b/lib/routes/acfun/video.ts
@@ -40,7 +40,7 @@ async function handler(ctx) {
const list = $('#ac-space-video-list a').toArray();
const image = $('head style:contains("user-photo")')
.text()
- .match(/.user-photo{\n\s*background:url\((.*)\) 0% 0% \/ 100% no-repeat;/)?.[1];
+ .match(/.user-photo\{\n\s*background:url\((.*)\) 0% 0% \/ 100% no-repeat;/)?.[1];
return {
title,
@@ -59,7 +59,7 @@ async function handler(ctx) {
return {
title: itemTitle,
- description: renderDescription({ embed, aid, img: itemImg?.split('?')[0] }),
+ description: renderDescription({ embed, aid, img: itemImg?.split('?', 1)[0] }),
link: host + itemUrl,
pubDate: parseDate(itemDate, 'YYYY/MM/DD'),
};
diff --git a/lib/routes/acgvinyl/news.ts b/lib/routes/acgvinyl/news.ts
index 3a8f618f0843..0b5e52622bc7 100644
--- a/lib/routes/acgvinyl/news.ts
+++ b/lib/routes/acgvinyl/news.ts
@@ -8,7 +8,7 @@ import { parseDate } from '@/utils/parse-date';
export const route: Route = {
path: '/news',
categories: ['anime'],
- example: '/news',
+ example: '/acgvinyl/news',
features: {
requireConfig: false,
requirePuppeteer: false,
@@ -40,6 +40,7 @@ async function handler(ctx) {
const newsIndexJsonText = $('script:contains("window.__INITIAL_STATE__")').text().replaceAll('window.__INITIAL_STATE__=', '');
const newsIndexJson = JSON.parse(newsIndexJsonText);
+ const limit = ctx.req.query('limit');
const newsListResponse = await ofetch(`${rootUrl}/rajax/news_h.jsp?cmd=getWafNotCk_getList`, {
method: 'POST',
headers: {
@@ -47,7 +48,7 @@ async function handler(ctx) {
},
body: new URLSearchParams({
page: '1',
- pageSize: String(ctx.req.query('limit') ?? 20),
+ pageSize: String(limit ?? 20),
fromMid: newsIndexJson.modules.module366.id,
idList: `[${newsIndexJson.modules.module366.prop3}]`,
sortKey: newsIndexJson.modules.module366.blob0.sortKey,
diff --git a/lib/routes/acpaa/index.ts b/lib/routes/acpaa/index.ts
index 60707c519458..b33d67603b1c 100644
--- a/lib/routes/acpaa/index.ts
+++ b/lib/routes/acpaa/index.ts
@@ -26,7 +26,7 @@ export const route: Route = {
async function handler(ctx) {
const { id = '1', name = '重要通知' } = ctx.req.param();
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
const rootUrl = 'http://www.acpaa.cn';
const currentUrl = new URL(`article/taglist.jhtml?tagIds=${id}&tagname=${name}`, rootUrl).href;
diff --git a/lib/routes/acs/journal.tsx b/lib/routes/acs/journal.tsx
index 4ad4d5587da1..d5f2b847817d 100644
--- a/lib/routes/acs/journal.tsx
+++ b/lib/routes/acs/journal.tsx
@@ -28,14 +28,14 @@ async function handler(ctx) {
let title = '';
- const browser = await playwright();
+ const context = await playwright();
const items = await cache.tryGet(
currentUrl,
async () => {
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' ? request.continue() : request.abort();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' ? route.continue() : route.abort();
});
await page.goto(currentUrl, {
waitUntil: 'domcontentloaded',
@@ -76,7 +76,7 @@ async function handler(ctx) {
false
);
- await browser.close();
+ await context.close();
return {
title,
diff --git a/lib/routes/adquan/case-library.ts b/lib/routes/adquan/case-library.ts
index 49bc2804798f..5264e229da8a 100644
--- a/lib/routes/adquan/case-library.ts
+++ b/lib/routes/adquan/case-library.ts
@@ -13,7 +13,7 @@ import timezone from '@/utils/timezone';
import { renderDescription } from './templates/description';
export const handler = async (ctx: Context): Promise => {
- const limit: number = Number.parseInt(ctx.req.query('limit') ?? '24', 10);
+ const limit = Number(ctx.req.query('limit') ?? '24');
const baseUrl = 'https://www.adquan.com';
const targetUrl: string = new URL('case_library/index', baseUrl).href;
diff --git a/lib/routes/adquan/index.ts b/lib/routes/adquan/index.ts
index 5a11ff58e430..aef56ed7e07c 100644
--- a/lib/routes/adquan/index.ts
+++ b/lib/routes/adquan/index.ts
@@ -13,7 +13,7 @@ import timezone from '@/utils/timezone';
import { renderDescription } from './templates/description';
export const handler = async (ctx: Context): Promise => {
- const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
+ const limit = Number(ctx.req.query('limit') ?? '30');
const baseUrl = 'https://www.adquan.com';
const targetUrl: string = baseUrl;
diff --git a/lib/routes/aeaweb/index.tsx b/lib/routes/aeaweb/index.tsx
index 6d264824f660..d3ae3a53af01 100644
--- a/lib/routes/aeaweb/index.tsx
+++ b/lib/routes/aeaweb/index.tsx
@@ -68,7 +68,7 @@ async function handler(ctx) {
item = $(item);
return {
- link: `${rootUrl}${item.attr('href').split('&')[0]}`,
+ link: `${rootUrl}${item.attr('href').split('&', 1)[0]}`,
};
});
diff --git a/lib/routes/aeon/utils.tsx b/lib/routes/aeon/utils.tsx
index 4ce6257dcf69..803d8fc35a92 100644
--- a/lib/routes/aeon/utils.tsx
+++ b/lib/routes/aeon/utils.tsx
@@ -109,7 +109,7 @@ export const getData = async (list) => {
item.enclosure_type = 'audio/mpeg';
} else if (data.image?.url) {
const imageUrl = data.image.url;
- const cleanImageUrl = imageUrl.split('?')[0].toLowerCase();
+ const cleanImageUrl = imageUrl.split('?', 1)[0].toLowerCase();
item.enclosure_url = imageUrl;
if (cleanImageUrl.endsWith('.jpg') || cleanImageUrl.endsWith('.jpeg')) {
diff --git a/lib/routes/aflcio/blog.ts b/lib/routes/aflcio/blog.ts
index 55fe58542489..d05d11a013de 100644
--- a/lib/routes/aflcio/blog.ts
+++ b/lib/routes/aflcio/blog.ts
@@ -10,7 +10,7 @@ import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
export const handler = async (ctx: Context): Promise => {
- const limit: number = Number.parseInt(ctx.req.query('limit') ?? '5', 10);
+ const limit = Number(ctx.req.query('limit') ?? '5');
const baseUrl = 'https://aflcio.org';
const targetUrl: string = new URL('blog', baseUrl).href;
diff --git a/lib/routes/agefans/update.ts b/lib/routes/agefans/update.ts
index c7dbf3dd33bc..6121640147ec 100644
--- a/lib/routes/agefans/update.ts
+++ b/lib/routes/agefans/update.ts
@@ -57,10 +57,12 @@ async function handler() {
const content = load(detailResponse.data);
content('img').each((_, ele) => {
- if (ele.attribs['data-original']) {
- ele.attribs.src = ele.attribs['data-original'];
- delete ele.attribs['data-original'];
+ if (!ele.attribs['data-original']) {
+ return;
}
+
+ ele.attribs.src = ele.attribs['data-original'];
+ delete ele.attribs['data-original'];
});
content('.video_detail_collect').remove();
diff --git a/lib/routes/agora0/pen0.ts b/lib/routes/agora0/pen0.ts
index 664e4aaeb2c1..8f65d5ee8a89 100644
--- a/lib/routes/agora0/pen0.ts
+++ b/lib/routes/agora0/pen0.ts
@@ -43,8 +43,8 @@ async function handler() {
return {
title: item.find('h3').text(),
link: item.find('h3 a').attr('href'),
- author: meta.split('|')[0].trim(),
- pubDate: parseDate(meta.split('|')[1].trim()),
+ author: meta.split('|', 1)[0].trim(),
+ pubDate: parseDate(meta.split('|', 2)[1].trim()),
};
});
diff --git a/lib/routes/agri/index.ts b/lib/routes/agri/index.ts
index 2e5697cb0bd5..308c0cb87786 100644
--- a/lib/routes/agri/index.ts
+++ b/lib/routes/agri/index.ts
@@ -10,7 +10,7 @@ import { renderDescription } from './templates/description';
export const handler = async (ctx) => {
const { category = 'zx/zxfb/' } = ctx.req.param();
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 10;
const rootUrl = 'http://www.agri.cn';
const currentUrl = new URL(category.endsWith('/') ? category : `${category}/`, rootUrl).href;
diff --git a/lib/routes/ai-bot/daily-ai-news.ts b/lib/routes/ai-bot/daily-ai-news.ts
index d2de7b0d21ae..13f42a0cf2ba 100755
--- a/lib/routes/ai-bot/daily-ai-news.ts
+++ b/lib/routes/ai-bot/daily-ai-news.ts
@@ -21,8 +21,8 @@ function parseDateString(dateStr: string, ctx: DateContext): Date | undefined {
return undefined;
}
- const month = Number.parseInt(match[1], 10);
- const day = Number.parseInt(match[2], 10);
+ const month = Number(match[1]);
+ const day = Number(match[2]);
// 检测跨年:如果当前日期比上一个日期大,说明跨年了
if (ctx.prevMonth > 0 && (month > ctx.prevMonth || (month === ctx.prevMonth && day > ctx.prevDay))) {
diff --git a/lib/routes/aibase/daily.ts b/lib/routes/aibase/daily.ts
index 9d83c5b60195..e3c7efcdf1bf 100644
--- a/lib/routes/aibase/daily.ts
+++ b/lib/routes/aibase/daily.ts
@@ -14,7 +14,7 @@ export const route: Route = {
maintainers: ['3tuuu'],
handler: async (ctx) => {
// 每页数量限制
- const limit = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
+ const limit = Number(ctx.req.query('limit') ?? '30');
// 用项目中已有的获取页面方法,获取页面以及 Token
const currentUrl = new URL('discover', rootUrl).href;
const currentHtml = await ofetch(currentUrl);
diff --git a/lib/routes/aibase/discover.ts b/lib/routes/aibase/discover.ts
index 14e27b256fab..fa413dbcc11d 100644
--- a/lib/routes/aibase/discover.ts
+++ b/lib/routes/aibase/discover.ts
@@ -10,7 +10,7 @@ export const handler = async (ctx) => {
const [pid, sid] = id?.split(/-/) ?? [undefined, undefined];
- const limit = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
+ const limit = Number(ctx.req.query('limit') ?? '30');
const currentUrl = new URL(`discover${id ? `/${id}` : ''}`, rootUrl).href;
diff --git a/lib/routes/aibase/news.ts b/lib/routes/aibase/news.ts
index 83cd1ebf0b1a..60e8cf444664 100644
--- a/lib/routes/aibase/news.ts
+++ b/lib/routes/aibase/news.ts
@@ -13,7 +13,7 @@ export const route: Route = {
maintainers: ['zreo0'],
handler: async (ctx) => {
// 每页数量限制
- const limit = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
+ const limit = Number(ctx.req.query('limit') ?? '30');
// 用项目中已有的获取页面方法,获取页面以及 Token
const currentUrl = new URL('discover', rootUrl).href;
const currentHtml = await ofetch(currentUrl);
diff --git a/lib/routes/aibase/topic.ts b/lib/routes/aibase/topic.ts
index e0ddf9ac9399..c7e6c623f1c6 100644
--- a/lib/routes/aibase/topic.ts
+++ b/lib/routes/aibase/topic.ts
@@ -8,7 +8,7 @@ import { buildApiUrl, processItems, rootUrl } from './util';
export const handler = async (ctx) => {
const { id, filter = 'id' } = ctx.req.param();
- const limit = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
+ const limit = Number(ctx.req.query('limit') ?? '30');
const currentUrl = new URL(id ? `topic/${id}` : 'discover', rootUrl).href;
diff --git a/lib/routes/ainvest/article.ts b/lib/routes/ainvest/article.ts
index 77f827f4b0d0..a638d3a357fb 100644
--- a/lib/routes/ainvest/article.ts
+++ b/lib/routes/ainvest/article.ts
@@ -26,7 +26,7 @@ export const route: Route = {
};
async function handler(ctx) {
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 5;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 5;
const items = await fetchContentItems([109], limit);
return {
diff --git a/lib/routes/ainvest/news.ts b/lib/routes/ainvest/news.ts
index cf45cb879db5..8aeb08e17822 100644
--- a/lib/routes/ainvest/news.ts
+++ b/lib/routes/ainvest/news.ts
@@ -28,7 +28,7 @@ export const route: Route = {
};
async function handler(ctx) {
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 5;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 5;
const streamIds = [109, 416, 438, 529, 721, 834, 835];
const items = await fetchContentItems(streamIds, limit);
diff --git a/lib/routes/aip/journal-pupp.ts b/lib/routes/aip/journal-pupp.ts
index 845661a89223..3e0412b5a127 100644
--- a/lib/routes/aip/journal-pupp.ts
+++ b/lib/routes/aip/journal-pupp.ts
@@ -18,12 +18,12 @@ const handler = async (ctx) => {
}
// use Playwright due to the obstacle by cloudflare challenge
- const browser = await playwright();
+ const context = await playwright();
const { jrnlName, list } = await cache.tryGet(
jrnlUrl,
async () => {
- const response = await playwrightGet(jrnlUrl, browser);
+ const response = await playwrightGet(jrnlUrl, context);
const $ = load(response);
const jrnlName = $('.header-journal-title').text();
const list = $('.card')
@@ -52,7 +52,7 @@ const handler = async (ctx) => {
false
);
- await browser.close();
+ await context.close();
return {
title: jrnlName,
diff --git a/lib/routes/aip/journal.ts b/lib/routes/aip/journal.ts
index 68bea00e2867..f5c718e57e89 100644
--- a/lib/routes/aip/journal.ts
+++ b/lib/routes/aip/journal.ts
@@ -43,7 +43,7 @@ async function handler(ctx) {
const $ = load(response);
const jrnlName = $('meta[property="og:title"]')
.attr('content')
- .match(/(?:[^=]*=)?\s*([^>]+)\s*/)[1];
+ .match(/(?:[^=]*=)?\s*([^>]+)/)[1];
const publication = $('.al-article-item-wrap.al-normal');
const list = publication.toArray().map((item) => {
diff --git a/lib/routes/aip/utils.tsx b/lib/routes/aip/utils.tsx
index ba592557f627..00826b987be3 100644
--- a/lib/routes/aip/utils.tsx
+++ b/lib/routes/aip/utils.tsx
@@ -1,11 +1,11 @@
import { renderToString } from 'hono/jsx/dom/server';
-const playwrightGet = async (url, browser) => {
- const page = await browser.newPage();
+const playwrightGet = async (url, context) => {
+ const page = await context.newPage();
// await page.setExtraHTTPHeaders({ referer: host });
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' ? request.continue() : request.abort();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' ? route.continue() : route.abort();
});
await page.goto(url, {
waitUntil: 'domcontentloaded',
diff --git a/lib/routes/air-level/levelrank.ts b/lib/routes/air-level/levelrank.ts
index 856466be7ba4..de24aca9bb5a 100644
--- a/lib/routes/air-level/levelrank.ts
+++ b/lib/routes/air-level/levelrank.ts
@@ -4,7 +4,7 @@ import type { Route } from '@/types';
import ofetch from '@/utils/ofetch'; // 统一使用的请求库
export const route: Route = {
- path: ['/rank/:status?'],
+ path: '/rank/:status?',
radar: [
{
source: ['m.air-level.com/rank/:status', 'm.air-level.com/rank'],
diff --git a/lib/routes/aisixiang/column.ts b/lib/routes/aisixiang/column.ts
index ba0d9f5363dd..537bec3e9b1e 100644
--- a/lib/routes/aisixiang/column.ts
+++ b/lib/routes/aisixiang/column.ts
@@ -28,7 +28,7 @@ export const route: Route = {
async function handler(ctx) {
const id = ctx.req.param('id');
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
const currentUrl = new URL(`/data/search?column=${id}`, rootUrl).href;
@@ -49,7 +49,7 @@ async function handler(ctx) {
return {
title: a.text(),
link: new URL(a.prop('href'), rootUrl).href,
- author: a.text().split(':')[0],
+ author: a.text().split(':', 1)[0],
pubDate: timezone(parseDate(item.find('span').text()), +8),
};
});
diff --git a/lib/routes/aisixiang/thinktank.ts b/lib/routes/aisixiang/thinktank.ts
index d32d70799685..5bf2966f2155 100644
--- a/lib/routes/aisixiang/thinktank.ts
+++ b/lib/routes/aisixiang/thinktank.ts
@@ -29,7 +29,7 @@ export const route: Route = {
async function handler(ctx) {
const { id, type = '' } = ctx.req.param();
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
const currentUrl = new URL(`thinktank/${id}.html`, rootUrl).href;
diff --git a/lib/routes/aisixiang/toplist.ts b/lib/routes/aisixiang/toplist.ts
index 3599fe035e94..510f90f37a71 100644
--- a/lib/routes/aisixiang/toplist.ts
+++ b/lib/routes/aisixiang/toplist.ts
@@ -19,7 +19,7 @@ export const route: Route = {
async function handler(ctx) {
const { id = '1', period = '1' } = ctx.req.param();
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
const currentUrl = new URL(`toplist${id ? `?id=${id}${id === '1' ? `&period=${period}` : ''}` : ''}`, rootUrl).href;
@@ -27,7 +27,7 @@ async function handler(ctx) {
const $ = load(response);
- const title = `${$('a.hl').text() || ''}${$('title').text().split('_')[0]}`;
+ const title = `${$('a.hl').text() || ''}${$('title').text().split('_', 1)[0]}`;
const items = $('div.tops_list')
.slice(0, limit)
diff --git a/lib/routes/aisixiang/utils.ts b/lib/routes/aisixiang/utils.ts
index 94ba3e62ecfd..84fc1e56b0e1 100644
--- a/lib/routes/aisixiang/utils.ts
+++ b/lib/routes/aisixiang/utils.ts
@@ -29,8 +29,8 @@ const ProcessFeed = (limit, tryGet, items) =>
.toArray()
.map((c) => content(c).text());
item.pubDate = timezone(parseDate(content('div.info').text().split('时间:').pop()), +8);
- item.upvotes = content('span.like-num').text() ? Number.parseInt(content('span.like-num').text(), 10) : 0;
- item.comments = commentMatches ? Number.parseInt(commentMatches[1], 10) : 0;
+ item.upvotes = content('span.like-num').text() ? Number(content('span.like-num').text()) : 0;
+ item.comments = commentMatches ? Number(commentMatches[1]) : 0;
return item;
})
diff --git a/lib/routes/aisixiang/zhuanti.ts b/lib/routes/aisixiang/zhuanti.ts
index d6a9741212bf..de3175a73f7b 100644
--- a/lib/routes/aisixiang/zhuanti.ts
+++ b/lib/routes/aisixiang/zhuanti.ts
@@ -31,7 +31,7 @@ export const route: Route = {
async function handler(ctx) {
const id = ctx.req.param('id');
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
const currentUrl = new URL(`zhuanti/${id}.html`, rootUrl).href;
@@ -52,7 +52,7 @@ async function handler(ctx) {
return {
title: a.text(),
link: new URL(a.prop('href'), rootUrl).href,
- author: a.text().split(':')[0],
+ author: a.text().split(':', 1)[0],
pubDate: timezone(parseDate(item.find('span').text()), +8),
};
});
diff --git a/lib/routes/ali213/news.ts b/lib/routes/ali213/news.ts
index f19844e862d4..1fd9538f7af5 100644
--- a/lib/routes/ali213/news.ts
+++ b/lib/routes/ali213/news.ts
@@ -14,7 +14,7 @@ import { renderDescription } from './templates/description';
export const handler = async (ctx: Context): Promise => {
const { category = 'new' } = ctx.req.param();
- const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
+ const limit = Number(ctx.req.query('limit') ?? '30');
const rootUrl = 'https://www.ali213.net';
const targetUrl: string = new URL(`news/${category.endsWith('/') ? category : `${category}/`}`, rootUrl).href;
@@ -134,7 +134,7 @@ export const handler = async (ctx: Context): Promise => {
...item,
title,
description,
- pubDate: timezone(parseDate($$('div.newstag_l').text().split(/\s/)[0]), +8),
+ pubDate: timezone(parseDate($$('div.newstag_l').text().split(/\s/, 1)[0]), +8),
content: {
html: description,
text: $$('div#Content').html() ?? '',
diff --git a/lib/routes/ali213/zl.ts b/lib/routes/ali213/zl.ts
index 13c7cdf1a64f..c93d750f982e 100644
--- a/lib/routes/ali213/zl.ts
+++ b/lib/routes/ali213/zl.ts
@@ -13,7 +13,7 @@ import { renderDescription } from './templates/description';
export const handler = async (ctx: Context): Promise => {
const { category } = ctx.req.param();
- const limit: number = Number.parseInt(ctx.req.query('limit') ?? '1', 10);
+ const limit = Number(ctx.req.query('limit') ?? '1');
const rootUrl = 'https://www.ali213.net';
const apiRootUrl = 'https://mp.ali213.net';
diff --git a/lib/routes/alicesoft/infomation.ts b/lib/routes/alicesoft/infomation.ts
index 0cffbbdf455f..305013091b97 100644
--- a/lib/routes/alicesoft/infomation.ts
+++ b/lib/routes/alicesoft/infomation.ts
@@ -36,7 +36,7 @@ export const route: Route = {
async function handler(ctx) {
const { category, game } = ctx.req.param();
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 10;
let url = `${baseUrl}/information`;
if (category) {
diff --git a/lib/routes/aljazeera/index.tsx b/lib/routes/aljazeera/index.tsx
index fb1f5a7cbf4b..1e64c901373e 100644
--- a/lib/routes/aljazeera/index.tsx
+++ b/lib/routes/aljazeera/index.tsx
@@ -71,8 +71,9 @@ async function handler(ctx) {
};
});
+ const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 50;
items = await Promise.all(
- items.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 50).map((item) =>
+ items.slice(0, limit).map((item) =>
cache.tryGet(item.link, async () => {
const detailResponse = await ofetch(item.link);
diff --git a/lib/routes/ally/rail.ts b/lib/routes/ally/rail.ts
index c7c67e5b886c..8f5d6dbc5144 100644
--- a/lib/routes/ally/rail.ts
+++ b/lib/routes/ally/rail.ts
@@ -83,7 +83,7 @@ async function handler(ctx) {
.filter(Boolean);
const uniqueItems: DataItem[] = [];
for (const item of items) {
- if (!uniqueItems.some((uniqueItem) => uniqueItem.link === item?.link)) {
+ if (uniqueItems.every((uniqueItem) => uniqueItem.link !== item?.link)) {
uniqueItems.push(item!);
}
}
diff --git a/lib/routes/alternativeto/utils.ts b/lib/routes/alternativeto/utils.ts
index 08fd66814fef..8160f19c5471 100644
--- a/lib/routes/alternativeto/utils.ts
+++ b/lib/routes/alternativeto/utils.ts
@@ -4,17 +4,17 @@ const baseURL = 'https://alternativeto.net';
const playwrightGet = (url, cache) =>
cache.tryGet(url, async () => {
- const browser = await playwright();
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' ? request.continue() : request.abort();
+ const context = await playwright();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' ? route.continue() : route.abort();
});
await page.goto(url, {
waitUntil: 'domcontentloaded',
});
const html = await page.evaluate(() => document.documentElement.innerHTML);
- await browser.close();
+ await context.close();
return html;
});
diff --git a/lib/routes/altotrain/news.ts b/lib/routes/altotrain/news.ts
index f541b8ed2bb3..8bfbe028df29 100644
--- a/lib/routes/altotrain/news.ts
+++ b/lib/routes/altotrain/news.ts
@@ -71,7 +71,7 @@ function extractItem(a: Cheerio
- Download: {item.DownloadUrl.Global.split('/').pop().split('?')[0]} + Download: {item.DownloadUrl.Global.split('/').pop().split('?', 1)[0]}
> ) diff --git a/lib/routes/atptour/news.ts b/lib/routes/atptour/news.ts index 062ebff64609..cc27869aad09 100644 --- a/lib/routes/atptour/news.ts +++ b/lib/routes/atptour/news.ts @@ -22,7 +22,7 @@ async function handler(ctx) { const baseUrl = 'https://www.atptour.com'; const favIcon = `${baseUrl}/assets/atptour/assets/favicon.ico`; const { lang = 'en' } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 15; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 15; const link = `${baseUrl}/${lang}/-/tour/news/latest-filtered-results/0/${limit}`; const { data } = await got(link, { diff --git a/lib/routes/augmentcode/blog.tsx b/lib/routes/augmentcode/blog.tsx index f8ad180f0bef..a0cfba8c5cb9 100644 --- a/lib/routes/augmentcode/blog.tsx +++ b/lib/routes/augmentcode/blog.tsx @@ -31,7 +31,7 @@ const renderDescription = ({ images, description }: { images?: DescriptionImage[ ); export const handler = async (ctx: Context): Promise => { - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '50', 10); + const limit = Number(ctx.req.query('limit') ?? '50'); const baseUrl = 'https://augmentcode.com'; const targetUrl: string = new URL('blog', baseUrl).href; diff --git a/lib/routes/auto-stats/index.ts b/lib/routes/auto-stats/index.ts index 6c51ebd95256..36bd61d41de2 100644 --- a/lib/routes/auto-stats/index.ts +++ b/lib/routes/auto-stats/index.ts @@ -30,7 +30,7 @@ export const route: Route = { async function handler(ctx) { const { category = 'xxkd' } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; const rootUrl = 'http://www.auto-stats.org.cn'; const currentUrl = new URL(`${category}.asp`, rootUrl).href; @@ -51,7 +51,7 @@ async function handler(ctx) { const pubDate = title.match(/(\d{4}(?:\/\d{1,2}){2}\s\d{1,2}(?::\d{2}){2})/)?.[1] ?? undefined; return { - title: title.replace(/●/, '').split(/(\d+/)[0], + title: title.replace(/●/, '').split(/(\d+/, 1)[0], link: new URL(item.parent().prop('href'), rootUrl).href, pubDate: timezone(parseDate(pubDate, 'YYYY/M/D H:mm:ss'), +8), }; diff --git a/lib/routes/azul/packages.ts b/lib/routes/azul/packages.ts index c8ecf5080d49..8a29eb08a8c3 100644 --- a/lib/routes/azul/packages.ts +++ b/lib/routes/azul/packages.ts @@ -7,7 +7,7 @@ import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; export const handler = async (ctx: Context): Promise => { - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); + const limit = Number(ctx.req.query('limit') ?? '30'); const baseUrl = 'https://www.azul.com'; const apiBaseUrl = 'https://api.azul.com'; diff --git a/lib/routes/baai/hub.ts b/lib/routes/baai/hub.ts index e0a39473c331..aebae88db66c 100644 --- a/lib/routes/baai/hub.ts +++ b/lib/routes/baai/hub.ts @@ -8,7 +8,7 @@ import ofetch from '@/utils/ofetch'; import { apiHost, baseUrl, getTagsData, parseEventDetail, parseItem } from './utils'; export const route: Route = { - path: ['/hub/:tagId?/:sort?/:range?'], + path: '/hub/:tagId?/:sort?/:range?', categories: ['programming'], example: '/baai/hub', parameters: { diff --git a/lib/routes/bandcamp/live.ts b/lib/routes/bandcamp/live.ts index a6c3ef81020e..03024db9f3a5 100644 --- a/lib/routes/bandcamp/live.ts +++ b/lib/routes/bandcamp/live.ts @@ -49,7 +49,7 @@ async function handler() { link: item.find('.title-link').attr('href'), title: item.find('.show-title').text(), author: item.find('.show-artist').text(), - pubDate: parseDate(item.find('.show-time-container').text().trim().split(' UTC')[0]), + pubDate: parseDate(item.find('.show-time-container').text().trim().split(' UTC', 1)[0]), description: `${await nextNode(node.content)}
`, text: (node) => { const { attributes: attr, value: val } = node; - if (attr?.emphasis && attr?.strong) { + if (attr?.emphasis && attr.strong) { return `${val}`; - } else if (attr?.emphasis) { + } + if (attr?.emphasis) { return `${val}`; - } else if (attr?.strong) { + } + if (attr?.strong) { return `${val}`; - } else { - return val; } + return val; }, 'inline-newsletter': async (node, nextNode) => `+ 地点: + {location} +
++ 开展: + {startDate ?? '未定/常设'} +
++ 闭展: + {endDate ?? '未定/常设'} +
+ {fullDuration && ( ++ 原始展期:{fullDuration} +
+ )} +').replaceAll(mentionPattern, ' @$1'); diff --git a/lib/routes/dedao/list.ts b/lib/routes/dedao/list.ts index ae6c6ad2daf9..9a1747174a53 100644 --- a/lib/routes/dedao/list.ts +++ b/lib/routes/dedao/list.ts @@ -45,7 +45,7 @@ async function handler(ctx) { url: listUrl, }); - const currentUrl = `${rootUrl}${listResponse.data.match(new RegExp('' + category + String.raw`<\/span><\/a>`))[1].split('"')[0]}`; + const currentUrl = `${rootUrl}${listResponse.data.match(new RegExp('' + category + String.raw`<\/span><\/a>`))[1].split('"', 1)[0]}`; const currentResponse = await got({ method: 'get', diff --git a/lib/routes/dedao/user.tsx b/lib/routes/dedao/user.tsx index 164618039768..39ebb3772c8d 100644 --- a/lib/routes/dedao/user.tsx +++ b/lib/routes/dedao/user.tsx @@ -11,7 +11,7 @@ const types = { 12: '视频', }; -const mentionPattern = /<\u2267\u2746>{"name":"(.*?)","uid":"\d+","at":"1"}<\/\u2266\u2746>/g; +const mentionPattern = /<\u2267\u2746>\{"name":"(.*?)","uid":"\d+","at":"1"\}<\/\u2266\u2746>/g; const formatNoteText = (text = '') => text.replaceAll('\n\n', '
').replaceAll(mentionPattern, ' @$1');
diff --git a/lib/routes/deepl/blog.ts b/lib/routes/deepl/blog.ts
index 99ab952f5a3e..91abcce2265c 100644
--- a/lib/routes/deepl/blog.ts
+++ b/lib/routes/deepl/blog.ts
@@ -13,7 +13,7 @@ import { renderDescription } from './templates/description';
export const handler = async (ctx: Context): Promise => {
const { lang = 'en' } = ctx.req.param();
- const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
+ const limit = Number(ctx.req.query('limit') ?? '30');
const baseUrl = 'https://www.deepl.com';
const targetUrl: string = new URL(`${lang}/blog`, baseUrl).href;
diff --git a/lib/routes/deeplearning/the-batch.ts b/lib/routes/deeplearning/the-batch.ts
index b82aedbae1f6..e07bc67548eb 100644
--- a/lib/routes/deeplearning/the-batch.ts
+++ b/lib/routes/deeplearning/the-batch.ts
@@ -9,7 +9,7 @@ import { renderDescription } from './templates/description';
export const handler = async (ctx) => {
const { tag } = ctx.req.param();
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 1;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 1;
const rootUrl = 'https://www.deeplearning.ai';
const currentUrl = new URL(`the-batch${tag ? `/tag/${tag.replace(/^tag\//, '').replace(/\/$/, '')}` : ''}/`, rootUrl).href;
@@ -73,14 +73,15 @@ export const handler = async (ctx) => {
const $$ = load(post.html);
$$('a').each((_, ele) => {
- if (ele.attribs.href?.includes('utm_campaign')) {
- const url = new URL(ele.attribs.href);
- url.searchParams.delete('utm_campaign');
- url.searchParams.delete('utm_source');
- url.searchParams.delete('utm_medium');
- url.searchParams.delete('_hsenc');
- ele.attribs.href = url.href;
+ if (!ele.attribs.href?.includes('utm_campaign')) {
+ return;
}
+ const url = new URL(ele.attribs.href);
+ url.searchParams.delete('utm_campaign');
+ url.searchParams.delete('utm_source');
+ url.searchParams.delete('utm_medium');
+ url.searchParams.delete('_hsenc');
+ ele.attribs.href = url.href;
});
const title = post.title;
diff --git a/lib/routes/dehenglaw/index.ts b/lib/routes/dehenglaw/index.ts
index c5966d874ace..65921cf78edb 100644
--- a/lib/routes/dehenglaw/index.ts
+++ b/lib/routes/dehenglaw/index.ts
@@ -9,7 +9,7 @@ import { renderDescription } from './templates/description';
export const handler = async (ctx) => {
const { language = 'CN', category = 'paper' } = ctx.req.param();
- const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 6;
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 6;
const rootUrl = 'https://www.dehenglaw.com';
const currentUrl = new URL(`${language}/${category}/0008/000901.aspx`, rootUrl).href;
@@ -70,7 +70,7 @@ export const handler = async (ctx) => {
return {
title: $('title')
.text()
- .replace(/\|.*?$/, `| ${$('li.onthis').text()}`),
+ .replace(/\|.*$/, () => `| ${$('li.onthis').text()}`),
description: $('meta[name="Description"]').prop('content'),
link: currentUrl,
item: items,
diff --git a/lib/routes/denonbu/news.ts b/lib/routes/denonbu/news.ts
index e3617d38b926..deadcf2e3590 100644
--- a/lib/routes/denonbu/news.ts
+++ b/lib/routes/denonbu/news.ts
@@ -117,9 +117,11 @@ async function getToken(): Promise
';
}
if (status.card.images_block) {
- const imageUrls: Array`,
- link: url,
- pubDate: new Date(day),
- };
+ const url = `https://movie.douban.com/people/${userid}/wish`;
+ const data = await cache.tryGet(
+ url,
+ async () => {
+ const _r = await got({
+ method: 'GET',
+ url,
+ headers: {
+ Referer: url,
+ Cookie: config.douban.cookie || '',
+ },
+ });
+ return _r.data;
+ },
+ config.cache.routeExpire,
+ false
+ );
+ const $ = load(data);
+ const username = $('div.side-info-txt > h3').text();
- return rssItem;
- })
- );
- }
- })
- );
- }
+ const items = $('div.article > div.grid-view > div.item')
+ .toArray()
+ .map((item) => {
+ item = $(item);
+ const itemPicUrl = item.find('.pic a img').attr('src');
+ const info = item.find('.info');
+ const title = info.find('ul li.title a').text();
+ const url = info.find('ul li.title a').attr('href');
+ const title_ = title.split('/').find((title) => title.trim());
+ const day = info.find('ul li .date').text().trim();
+ return {
+ title: title_,
+ description: `${info.find('.intro').text()}
`,
+ link: url,
+ pubDate: new Date(day),
+ };
+ });
- const items = (await Promise.all(tasks)).flat();
return {
- title: `豆瓣想看 - ${userName || userid}`,
+ title: `豆瓣想看 - ${username || userid}`,
link: `https://movie.douban.com/people/${userid}/wish`,
item: items,
};
diff --git a/lib/routes/douban/tv/coming.ts b/lib/routes/douban/tv/coming.ts
index 1b4581383b28..33fa97c18ff1 100644
--- a/lib/routes/douban/tv/coming.ts
+++ b/lib/routes/douban/tv/coming.ts
@@ -55,27 +55,27 @@ const getPubDate = (pubdate?: string[]): Date | undefined => {
return undefined;
}
- const datePart = pubDateText.split('(')[0];
+ const datePart = pubDateText.split('(', 1)[0];
return parseDate(datePart);
};
const getSortTimestamp = (pubdate?: string[]): number => {
const pubDateText = getPubDateText(pubdate);
if (!pubDateText) {
- return Number.POSITIVE_INFINITY;
+ return Infinity;
}
- const datePart = pubDateText.split('(')[0].trim();
+ const datePart = pubDateText.split('(', 1)[0].trim();
const match = /^(\d{4})(?:-(\d{1,2}))?(?:-(\d{1,2}))?/.exec(datePart);
if (!match) {
- return Number.POSITIVE_INFINITY;
+ return Infinity;
}
- const year = Number.parseInt(match[1], 10);
- const month = match[2] ? Number.parseInt(match[2], 10) : 1;
- const day = match[3] ? Number.parseInt(match[3], 10) : 1;
+ const year = Number(match[1]);
+ const month = match[2] ? Number(match[2]) : 1;
+ const day = match[3] ? Number(match[3]) : 1;
const timestamp = Date.UTC(year, month - 1, day);
- return Number.isNaN(timestamp) ? Number.POSITIVE_INFINITY : timestamp;
+ return Number.isNaN(timestamp) ? Infinity : timestamp;
};
const getWishCount = (wishCount?: number | string): number => {
@@ -83,7 +83,7 @@ const getWishCount = (wishCount?: number | string): number => {
return wishCount;
}
if (typeof wishCount === 'string') {
- const parsed = Number.parseInt(wishCount, 10);
+ const parsed = Number(wishCount);
return Number.isNaN(parsed) ? 0 : parsed;
}
return 0;
@@ -146,7 +146,7 @@ async function handler(ctx) {
const countParam = ctx.req.param('count');
const sortBy = sortByParam === 'time' ? 'time' : 'hot';
- const rawCount = Number.parseInt(countParam || '', 10);
+ const rawCount = Number(countParam || '');
const requestCount = Number.isNaN(rawCount) || rawCount <= 0 ? 10 : rawCount;
const ts = new Date().toISOString().slice(0, 10).replaceAll('-', '');
diff --git a/lib/routes/douyin/hashtag.ts b/lib/routes/douyin/hashtag.ts
index 47f3f01fa9e3..77541e05e406 100644
--- a/lib/routes/douyin/hashtag.ts
+++ b/lib/routes/douyin/hashtag.ts
@@ -47,12 +47,12 @@ async function handler(ctx) {
const tagData = await cache.tryGet(
`douyin:hashtag:${cid}`,
async () => {
- const browser = await playwright();
- const page = await browser.newPage();
- await page.setRequestInterception(true);
+ const context = await playwright();
+ const page = await context.newPage();
let awemeList = '';
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' || request.resourceType() === 'xhr' ? request.continue() : request.abort();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' || request.resourceType() === 'xhr' ? route.continue() : route.abort();
});
page.on('response', async (response) => {
const request = response.request();
@@ -61,11 +61,11 @@ async function handler(ctx) {
}
});
await page.goto(tagUrl, {
- waitUntil: 'networkidle2',
+ waitUntil: 'networkidle',
});
await page.waitForSelector('#RENDER_DATA');
const html = await page.evaluate(() => document.querySelector('#RENDER_DATA').textContent);
- await browser.close();
+ await context.close();
const renderData = JSON.parse(decodeURIComponent(html));
const dataKey = Object.keys(renderData).find((key) => renderData[key].topicDetail);
diff --git a/lib/routes/douyin/live.ts b/lib/routes/douyin/live.ts
index c1d3f7a438b4..93cb864a36f3 100644
--- a/lib/routes/douyin/live.ts
+++ b/lib/routes/douyin/live.ts
@@ -42,12 +42,11 @@ async function handler(ctx) {
`douyin:live:${rid}`,
async () => {
let roomInfo;
- const browser = await playwright();
- const page = await browser.newPage();
- await page.setRequestInterception(true);
-
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'stylesheet' || request.resourceType() === 'script' || request.resourceType() === 'xhr' ? request.continue() : request.abort();
+ const context = await playwright();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'stylesheet' || request.resourceType() === 'script' || request.resourceType() === 'xhr' ? route.continue() : route.abort();
});
page.on('response', async (response) => {
const request = response.request();
@@ -57,9 +56,9 @@ async function handler(ctx) {
});
logger.http(`Requesting ${pageUrl}`);
await page.goto(pageUrl, {
- waitUntil: 'networkidle2',
+ waitUntil: 'networkidle',
});
- await browser.close();
+ await context.close();
return roomInfo;
},
diff --git a/lib/routes/douyin/user.ts b/lib/routes/douyin/user.ts
index bbfbb8edc50f..ca0a020e8ed5 100644
--- a/lib/routes/douyin/user.ts
+++ b/lib/routes/douyin/user.ts
@@ -50,12 +50,11 @@ async function handler(ctx) {
`douyin:user:${uid}`,
async () => {
let postData;
- const browser = await playwright();
- const page = await browser.newPage();
- await page.setRequestInterception(true);
-
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' || request.resourceType() === 'xhr' ? request.continue() : request.abort();
+ const context = await playwright();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' || request.resourceType() === 'xhr' ? route.continue() : route.abort();
});
page.on('response', async (response) => {
const request = response.request();
@@ -66,10 +65,10 @@ async function handler(ctx) {
logger.http(`Requesting ${pageUrl}`);
await page.goto(pageUrl, {
- waitUntil: 'networkidle2',
+ waitUntil: 'networkidle',
});
- await browser.close();
+ await context.close();
if (!postData) {
throw new Error('Empty post data. The request may be filtered by WAF.');
@@ -114,7 +113,7 @@ async function handler(ctx) {
const description = templates.desc({ desc, media });
return {
- title: post.desc.split('\n')[0],
+ title: post.desc.split('\n', 1)[0],
description,
link: `https://www.douyin.com/video/${post.aweme_id}`,
pubDate: parseDate(post.create_time * 1000),
diff --git a/lib/routes/douyin/utils.ts b/lib/routes/douyin/utils.ts
index 4776659d12c9..a08dd4765bbf 100644
--- a/lib/routes/douyin/utils.ts
+++ b/lib/routes/douyin/utils.ts
@@ -32,12 +32,11 @@ const proxyVideo = (url, proxy) => {
proxy += '=';
}
return proxy + encodeURIComponent(url);
- } else {
- if (!proxy.endsWith('/')) {
- proxy += '/';
- }
- return proxy + url;
}
+ if (!proxy.endsWith('/')) {
+ proxy += '/';
+ }
+ return proxy + url;
};
const getOriginAvatar = (url) =>
diff --git a/lib/routes/dpm/exhibitions.tsx b/lib/routes/dpm/exhibitions.tsx
index e33df05dce79..5aa2a3de4782 100644
--- a/lib/routes/dpm/exhibitions.tsx
+++ b/lib/routes/dpm/exhibitions.tsx
@@ -66,7 +66,7 @@ export const route: Route = {
});
const museumName = namespace.zh?.name || namespace.name;
- const titleTag = currentType ? `${currentType.name}` : '正在展览';
+ const titleTag = currentType ? currentType.name : '正在展览';
const $ = load(response.data);
const itemElements = $('.item').toArray();
diff --git a/lib/routes/dribbble/utils.tsx b/lib/routes/dribbble/utils.tsx
index c7438b556977..727b3836e658 100644
--- a/lib/routes/dribbble/utils.tsx
+++ b/lib/routes/dribbble/utils.tsx
@@ -17,7 +17,7 @@ async function loadContent(link) {
const shotData = JSON.parse(
$('script')
.text()
- .match(/shotData:\s({.+?}),\n/)?.[1] ?? '{}'
+ .match(/shotData:\s(\{.+?\}),\n/)?.[1] ?? '{}'
);
// Join multiple shots together by selecting elements with class 'media-shot' or 'main-shot' or 'block-media-wrapper'
@@ -51,11 +51,11 @@ async function loadContent(link) {
}
if (!img.attr('src') && img.data('src')) {
- img.attr('src', img.data('src').split('?')[0]);
+ img.attr('src', img.data('src').split('?', 1)[0]);
img.removeAttr('data-src');
}
- img.attr('src', img.attr('src').split('?')[0]);
+ img.attr('src', img.attr('src').split('?', 1)[0]);
img.removeAttr('srcset');
img.removeAttr('data-srcset');
});
diff --git a/lib/routes/duozhi/index.ts b/lib/routes/duozhi/index.ts
index 8f4358529458..1e873ea0ccd0 100644
--- a/lib/routes/duozhi/index.ts
+++ b/lib/routes/duozhi/index.ts
@@ -14,7 +14,7 @@ import { renderDescription } from './templates/description';
export const handler = async (ctx: Context): Promise => {
const { category } = ctx.req.param();
- const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
+ const limit = Number(ctx.req.query('limit') ?? '30');
const baseUrl = 'http://www.duozhi.com';
const targetUrl: string = new URL(category && category.endsWith('/') ? category : category ? `${category}/` : '', baseUrl).href;
@@ -45,7 +45,7 @@ export const handler = async (ctx: Context): Promise => {
]
: undefined,
});
- const pubDateStr: string | undefined = $el.find('div.post-attr').text().split(/\|/)[0]?.trim();
+ const pubDateStr: string | undefined = $el.find('div.post-attr').text().split(/\|/, 1)[0]?.trim();
const linkUrl: string | undefined = $aEl.attr('href');
const categoryEls: Element[] = $el.find('span.post-tag a.link-tag').toArray();
const categories: string[] = [...new Set(categoryEls.map((el) => $(el).text()).filter(Boolean))];
@@ -109,7 +109,7 @@ export const handler = async (ctx: Context): Promise => {
});
const pubDateStr: string | undefined = $$('div.subject-meta')
.text()
- ?.split(/发布/)[0];
+ ?.split(/发布/, 1)[0];
const categories: string[] = [
...new Set([
...(item.category ?? []),
diff --git a/lib/routes/duozhuayu/search.tsx b/lib/routes/duozhuayu/search.tsx
index 1a977755ba04..0b6a743f4ead 100644
--- a/lib/routes/duozhuayu/search.tsx
+++ b/lib/routes/duozhuayu/search.tsx
@@ -1,5 +1,5 @@
-/* eslint-disable unicorn/prefer-code-point */
-import aesjs from 'aes-js';
+import crypto from 'node:crypto';
+
import { renderToString } from 'hono/jsx/dom/server';
import type { Route } from '@/types';
@@ -29,6 +29,25 @@ export const route: Route = {
handler,
};
+const generateDeviceId = () => {
+ const alphabet = 'abcdefghijklmnopqrstuvwxyz234567';
+ let value = 0;
+ let bits = 0;
+ let id = 'b';
+ for (const byte of crypto.randomBytes(16)) {
+ value = (value << 8) | byte;
+ bits += 8;
+ while (bits >= 5) {
+ bits -= 5;
+ id += alphabet[(value >> bits) & 31];
+ }
+ }
+ if (bits > 0) {
+ id += alphabet[(value << (5 - bits)) & 31];
+ }
+ return id;
+};
+
async function handler(ctx) {
const wd = ctx.req.param('wd');
const baseUrl = 'https://www.duozhuayu.com';
@@ -36,14 +55,9 @@ async function handler(ctx) {
const link = `${baseUrl}/search/${type}/${wd}`;
// token获取见 https://github.com/wong2/userscripts/blob/master/duozhuayu.user.js
- const key = [...'DkOliWvFNR7C4WvR'].map((c) => c.charCodeAt());
- const iv = [...'GQWKUE2CVGOOBKXU'].map((c) => c.charCodeAt());
- const aesCfb = new aesjs.ModeOfOperation.cfb(key, iv);
-
const encrypt = (text) => {
- const textBytes = aesjs.utils.utf8.toBytes(text);
- const encryptedBytes = aesCfb.encrypt(textBytes);
- return aesjs.utils.hex.fromBytes(encryptedBytes);
+ const cipher = crypto.createCipheriv('aes-128-cfb8', 'DkOliWvFNR7C4WvR', 'GQWKUE2CVGOOBKXU');
+ return Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]).toString('hex');
};
const getCustomRequestHeaders = () => {
@@ -53,10 +67,13 @@ async function handler(ctx) {
const token = encrypt([timestamp, userId, securityKey].join(':'));
const requestId = [userId, timestamp, Math.round(1e5 * Math.random())].join('-');
return {
- 'x-api-version': '0.0.48',
+ 'x-api-version': '0.0.85',
+ 'x-app-platform': 'na',
+ 'x-app-version': 'na',
+ 'x-device-id': generateDeviceId(),
'x-refer-request-id': requestId,
'x-request-id': requestId,
- 'x-request-misc': '{"platform":"browser","originSource":"search","originFrom":"normal","webVersion":"1.2.201774"}',
+ 'x-request-misc': '{"platform":"browser","originSource":"search","originFrom":"normal","webVersion":"1.2.525412"}',
'x-request-token': token,
'x-security-key': securityKey,
'x-timestamp': timestamp,
@@ -76,7 +93,8 @@ async function handler(ctx) {
const item = response.data.data
.filter((item) => item.type === type)
- .map(({ [type]: item }) => ({
+ .map((entry) => entry[type])
+ .map((item) => ({
title: item.title,
link: `${baseUrl}/books/${item.id}`,
pubDate: parseDate(item.updated), // 2023-05-07T13:33:09+08:00
diff --git a/lib/routes/dut/index.ts b/lib/routes/dut/index.ts
index 117bf63e663e..870fc93e1891 100644
--- a/lib/routes/dut/index.ts
+++ b/lib/routes/dut/index.ts
@@ -25,7 +25,7 @@ async function handler(ctx) {
let items;
let category = ctx.params[1] ?? (Object.hasOwn(defaults, site) ? defaults[site] : '');
- category = Object.hasOwn(shortcuts, site) ? (Object.hasOwn(shortcuts[site], category) ? shortcuts[site][category] : category) : category;
+ category = Object.hasOwn(shortcuts, site) && Object.hasOwn(shortcuts[site], category) ? shortcuts[site][category] : category;
const rootUrl = `https://${site}.dlut.edu.cn`;
const currentUrl = `${rootUrl}/${category}.htm`;
diff --git a/lib/routes/dytt/index.ts b/lib/routes/dytt/index.ts
index d62cccaabbe1..882d20c27233 100644
--- a/lib/routes/dytt/index.ts
+++ b/lib/routes/dytt/index.ts
@@ -16,7 +16,7 @@ const baseUrl = `https://${domain}`;
export const handler = async (ctx: Context): Promise => {
const { category = 'gndy/dyzz' } = ctx.req.param();
- const limit: number = Number.parseInt(ctx.req.query('limit') ?? '25', 10);
+ const limit = Number(ctx.req.query('limit') ?? '25');
const targetUrl: string = new URL(`html/${category.replace(/^html\//, '')}`, baseUrl).href;
diff --git a/lib/routes/e-hentai/index.tsx b/lib/routes/e-hentai/index.tsx
index a426901f8793..fb5c2073ab74 100644
--- a/lib/routes/e-hentai/index.tsx
+++ b/lib/routes/e-hentai/index.tsx
@@ -50,7 +50,7 @@ async function handler(ctx) {
.toArray()
.map((tag) => $(tag).attr('title').replace(/^:/, '')),
description: needImages ? '' : `
`,
- enclosure_url: needTorrents ? (item.find('div.gldown a img[title="Show torrents"]').length > 0 ? item.find('.gldown a').attr('href') : undefined) : undefined,
+ enclosure_url: needTorrents && item.find('div.gldown a img[title="Show torrents"]').length > 0 ? item.find('.gldown a').attr('href') : undefined,
};
});
diff --git a/lib/routes/eastday/24.ts b/lib/routes/eastday/24.ts
index 43ed7a699e04..087f48ff9a34 100644
--- a/lib/routes/eastday/24.ts
+++ b/lib/routes/eastday/24.ts
@@ -92,7 +92,7 @@ async function handler(ctx) {
const links = [];
for (let i = 2; i <= pageNumber; i++) {
- links.push(item.link.replace(/\.html/, `-${i}.html`));
+ links.push(item.link.replace(/\.html/, () => `-${i}.html`));
}
for (const link of links) {
diff --git a/lib/routes/ecnu/art.ts b/lib/routes/ecnu/art.ts
index ed057daaac8d..c4afb5bd02c8 100644
--- a/lib/routes/ecnu/art.ts
+++ b/lib/routes/ecnu/art.ts
@@ -29,7 +29,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('span').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -44,16 +44,15 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
return item;
- } else {
- // file to download
- item.description = '请到原网页访问';
- return item;
}
+ // file to download
+ item.description = '请到原网页访问';
+ return item;
})
)
);
diff --git a/lib/routes/ecnu/bksy.ts b/lib/routes/ecnu/bksy.ts
index 6b31b948bf44..e08af2e1415d 100644
--- a/lib/routes/ecnu/bksy.ts
+++ b/lib/routes/ecnu/bksy.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_date').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('.news_title').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/cee.ts b/lib/routes/ecnu/cee.ts
index f32cca255fa5..f1b4530f3ad8 100644
--- a/lib/routes/ecnu/cee.ts
+++ b/lib/routes/ecnu/cee.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/chem.ts b/lib/routes/ecnu/chem.ts
index 720e02b91c38..53405feb8884 100644
--- a/lib/routes/ecnu/chem.ts
+++ b/lib/routes/ecnu/chem.ts
@@ -29,7 +29,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.cols_meta').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -44,16 +44,15 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
return item;
- } else {
- // file to download
- item.description = '请到原网页访问';
- return item;
}
+ // file to download
+ item.description = '请到原网页访问';
+ return item;
})
)
);
diff --git a/lib/routes/ecnu/chinese.ts b/lib/routes/ecnu/chinese.ts
index fbaf0a7fc591..36c379fe88f5 100644
--- a/lib/routes/ecnu/chinese.ts
+++ b/lib/routes/ecnu/chinese.ts
@@ -29,7 +29,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').attr('title'),
}));
const items = await Promise.all(
@@ -44,16 +44,15 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
return item;
- } else {
- // file to download
- item.description = '请到原网页访问';
- return item;
}
+ // file to download
+ item.description = '请到原网页访问';
+ return item;
})
)
);
diff --git a/lib/routes/ecnu/comm.ts b/lib/routes/ecnu/comm.ts
index 8c3d3e0d1873..3785a6c98986 100644
--- a/lib/routes/ecnu/comm.ts
+++ b/lib/routes/ecnu/comm.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/cs.ts b/lib/routes/ecnu/cs.ts
index d17599109acc..e4cbabdb5508 100644
--- a/lib/routes/ecnu/cs.ts
+++ b/lib/routes/ecnu/cs.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('span').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/cxcy.ts b/lib/routes/ecnu/cxcy.ts
index 0f5ef60fe78e..decc0f4977ac 100644
--- a/lib/routes/ecnu/cxcy.ts
+++ b/lib/routes/ecnu/cxcy.ts
@@ -50,7 +50,7 @@ export const route: Route = {
const filteredEls = $(`div.limit_style1[frag="${fragList[type].frag}"]`).find('table > tbody > tr > td').toArray();
const links = filteredEls.map((el) => ({
pubDate: timezone(parseDate($(el).find('.data').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('.news_title').text(),
}));
const items = await Promise.all(
@@ -65,16 +65,15 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
return item;
- } else {
- // file to download
- item.description = '请到原网页访问';
- return item;
}
+ // file to download
+ item.description = '请到原网页访问';
+ return item;
})
)
);
diff --git a/lib/routes/ecnu/dase.ts b/lib/routes/ecnu/dase.ts
index b4db79361156..6bb77f0ff81c 100644
--- a/lib/routes/ecnu/dase.ts
+++ b/lib/routes/ecnu/dase.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/dx.ts b/lib/routes/ecnu/dx.ts
index 45ddc286d073..8c6fe80bf16e 100644
--- a/lib/routes/ecnu/dx.ts
+++ b/lib/routes/ecnu/dx.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/dxb.ts b/lib/routes/ecnu/dxb.ts
index c7f8c0718290..3bbf32368b6d 100644
--- a/lib/routes/ecnu/dxb.ts
+++ b/lib/routes/ecnu/dxb.ts
@@ -29,7 +29,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -44,16 +44,15 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
return item;
- } else {
- // file to download
- item.description = '请到原网页访问';
- return item;
}
+ // file to download
+ item.description = '请到原网页访问';
+ return item;
})
)
);
diff --git a/lib/routes/ecnu/ed.ts b/lib/routes/ecnu/ed.ts
index 0d420244a435..0312caf9fcfb 100644
--- a/lib/routes/ecnu/ed.ts
+++ b/lib/routes/ecnu/ed.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/geoai.ts b/lib/routes/ecnu/geoai.ts
index e5578ad9835e..c67e30bb96f1 100644
--- a/lib/routes/ecnu/geoai.ts
+++ b/lib/routes/ecnu/geoai.ts
@@ -29,7 +29,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -44,16 +44,15 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
return item;
- } else {
- // file to download
- item.description = '点击认证后访问内容';
- return item;
}
+ // file to download
+ item.description = '点击认证后访问内容';
+ return item;
})
)
);
diff --git a/lib/routes/ecnu/ghcollege.ts b/lib/routes/ecnu/ghcollege.ts
index 3deef4413d0c..2271399b12e9 100644
--- a/lib/routes/ecnu/ghcollege.ts
+++ b/lib/routes/ecnu/ghcollege.ts
@@ -29,7 +29,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.Article_PublishDate').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -44,16 +44,15 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
return item;
- } else {
- // file to download
- item.description = '请至原网页访问内容';
- return item;
}
+ // file to download
+ item.description = '请至原网页访问内容';
+ return item;
})
)
);
diff --git a/lib/routes/ecnu/history.ts b/lib/routes/ecnu/history.ts
index d9eae7a3df68..a48695bbc14b 100644
--- a/lib/routes/ecnu/history.ts
+++ b/lib/routes/ecnu/history.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('span').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/jiaoliu.ts b/lib/routes/ecnu/jiaoliu.ts
index 98741a34ae7d..2b3bd4512bfb 100644
--- a/lib/routes/ecnu/jiaoliu.ts
+++ b/lib/routes/ecnu/jiaoliu.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('div[style="white-space:nowrap"]').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/jwc.ts b/lib/routes/ecnu/jwc.ts
index 56d965a319d6..9b11ae342719 100644
--- a/lib/routes/ecnu/jwc.ts
+++ b/lib/routes/ecnu/jwc.ts
@@ -33,7 +33,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_date').text()), 8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -43,7 +43,11 @@ export const route: Route = {
try {
const { data } = await got(item.link);
const $ = load(data);
- item.description = $('div.article')?.html()?.replaceAll('src="/', `src="${baseUrl}/`)?.replaceAll('href="/', `href="${baseUrl}/`)?.trim();
+ item.description = $('div.article')
+ ?.html()
+ ?.replaceAll('src="/', () => `src="${baseUrl}/`)
+ ?.replaceAll('href="/', () => `href="${baseUrl}/`)
+ ?.trim();
return item;
} catch {
// intranet
diff --git a/lib/routes/ecnu/mks.ts b/lib/routes/ecnu/mks.ts
index 891d801d97d7..52226a2b1868 100644
--- a/lib/routes/ecnu/mks.ts
+++ b/lib/routes/ecnu/mks.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_date').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('.news_title').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/mxcsy.ts b/lib/routes/ecnu/mxcsy.ts
index 1755a8b0401a..14bb6fd346b9 100644
--- a/lib/routes/ecnu/mxcsy.ts
+++ b/lib/routes/ecnu/mxcsy.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.Article_PublishDate').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/pharm.ts b/lib/routes/ecnu/pharm.ts
index b796b176bf54..b39c14f6dfd2 100644
--- a/lib/routes/ecnu/pharm.ts
+++ b/lib/routes/ecnu/pharm.ts
@@ -29,7 +29,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -44,16 +44,15 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
return item;
- } else {
- // file to download
- item.description = '请到原网页访问';
- return item;
}
+ // file to download
+ item.description = '请到原网页访问';
+ return item;
})
)
);
diff --git a/lib/routes/ecnu/philo.ts b/lib/routes/ecnu/philo.ts
index 446532cba1ca..a567c95900ed 100644
--- a/lib/routes/ecnu/philo.ts
+++ b/lib/routes/ecnu/philo.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('span').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/phy.ts b/lib/routes/ecnu/phy.ts
index a369a99d3d39..e070a50b4de0 100644
--- a/lib/routes/ecnu/phy.ts
+++ b/lib/routes/ecnu/phy.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('span').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').attr('title'),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/psy.ts b/lib/routes/ecnu/psy.ts
index 6d71ba87e024..2fed52f9c31e 100644
--- a/lib/routes/ecnu/psy.ts
+++ b/lib/routes/ecnu/psy.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.Article_PublishDate').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').attr('title'),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/sees.ts b/lib/routes/ecnu/sees.ts
index 1c5cd682f465..429c71dec9a4 100644
--- a/lib/routes/ecnu/sees.ts
+++ b/lib/routes/ecnu/sees.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/sei.ts b/lib/routes/ecnu/sei.ts
index 2040bd15a025..26973570a924 100644
--- a/lib/routes/ecnu/sei.ts
+++ b/lib/routes/ecnu/sei.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.data-list-time').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/spm.ts b/lib/routes/ecnu/spm.ts
index e83815416737..8e1e7f73dd96 100644
--- a/lib/routes/ecnu/spm.ts
+++ b/lib/routes/ecnu/spm.ts
@@ -29,7 +29,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -44,16 +44,15 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
return item;
- } else {
- // file to download
- item.description = '请到原网页访问';
- return item;
}
+ // file to download
+ item.description = '请到原网页访问';
+ return item;
})
)
);
diff --git a/lib/routes/ecnu/stat.ts b/lib/routes/ecnu/stat.ts
index 634568960b8b..15f0e70908f8 100644
--- a/lib/routes/ecnu/stat.ts
+++ b/lib/routes/ecnu/stat.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').attr('title'),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/ecnu/tyxx.ts b/lib/routes/ecnu/tyxx.ts
index 7ae1adffaa11..a257b7ada49a 100644
--- a/lib/routes/ecnu/tyxx.ts
+++ b/lib/routes/ecnu/tyxx.ts
@@ -27,7 +27,7 @@ export const route: Route = {
.toArray()
.map((el) => ({
pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8),
- link: new URL($(el).find('a').attr('href'), baseUrl).toString(),
+ link: new URL($(el).find('a').attr('href'), baseUrl).href,
title: $(el).find('a').text(),
}));
const items = await Promise.all(
@@ -41,7 +41,7 @@ export const route: Route = {
const attr = el.tagName === 'img' ? 'src' : 'href';
const val = $el.attr(attr);
if (val) {
- $el.attr(attr, new URL(val, baseUrl).toString());
+ $el.attr(attr, new URL(val, baseUrl).href);
}
});
item.description = $read.html()?.trim();
diff --git a/lib/routes/economist/full.ts b/lib/routes/economist/full.ts
index 657b0f5a281b..814ce9e2cd2e 100644
--- a/lib/routes/economist/full.ts
+++ b/lib/routes/economist/full.ts
@@ -49,8 +49,9 @@ async function handler(ctx) {
const endpoint = ctx.req.param('endpoint');
const feed = await parser.parseURL(`https://www.economist.com/${endpoint}/rss.xml`);
+ const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
const items = await Promise.all(
- feed.items.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30).map(async (item) => {
+ feed.items.slice(0, limit).map(async (item) => {
const path = item.link.slice(item.link.lastIndexOf('/') + 1);
const isNotCollection = !/^\d{4}-\d{2}-\d{2}$/.test(path);
const itemDetails = isNotCollection ? await getArticleDetail(item.link) : null;
diff --git a/lib/routes/economist/global-business-review.ts b/lib/routes/economist/global-business-review.ts
index 8dff9446e7e7..1cd0bd0be7a7 100644
--- a/lib/routes/economist/global-business-review.ts
+++ b/lib/routes/economist/global-business-review.ts
@@ -104,8 +104,9 @@ async function handler(ctx) {
url: 'https://api.hummingbird.businessreview.global/api/toc/get_articles',
});
+ const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10;
const items = await Promise.all(
- response.data.articles.new.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10).map(async (item) => ({
+ response.data.articles.new.slice(0, limit).map(async (item) => ({
title: parseTitle(item.body.title, [main_language]),
description: await getArticleDetail(item.article_id, language),
category: parseTitle(item.body.fly_title, [main_language]),
diff --git a/lib/routes/eeo/kuaixun.ts b/lib/routes/eeo/kuaixun.ts
index 28cf67b811ba..620a3e9707c1 100644
--- a/lib/routes/eeo/kuaixun.ts
+++ b/lib/routes/eeo/kuaixun.ts
@@ -12,7 +12,7 @@ import timezone from '@/utils/timezone';
import { renderDescription } from './templates/description';
export const handler = async (ctx: Context): Promise => {
- const limit: number = Number.parseInt(ctx.req.query('limit') ?? '50', 10);
+ const limit = Number(ctx.req.query('limit') ?? '50');
const baseUrl = 'https://www.eeo.com.cn';
const apiUrl = 'https://app.eeo.com.cn';
diff --git a/lib/routes/efe/index.ts b/lib/routes/efe/index.ts
new file mode 100644
index 000000000000..73ffeb9589f0
--- /dev/null
+++ b/lib/routes/efe/index.ts
@@ -0,0 +1,97 @@
+import { load } from 'cheerio';
+import pMap from 'p-map';
+
+import type { Route } from '@/types';
+import cache from '@/utils/cache';
+import ofetch from '@/utils/ofetch';
+import { parseDate } from '@/utils/parse-date';
+
+const rootUrl = 'https://efe.com';
+
+const categories: Record