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/VOUCHED.td b/.github/VOUCHED.td index 280c46e6ae89..e19d0249c864 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -7,10 +7,12 @@ # - Optional platform prefix: platform:username (e.g., github:user). # - Denounce with minus prefix: -username or -platform:username. # - Optional details after a space following the handle. +-an-lee impersonate as enpitsulin and DIYgod in #22205. 3a07e45a99c5300f47dadb2e91b40334626759d5. -betterandbetterii impersonate as dvorak0, goestav, Kjasn, Loongphy, TonyRL, ttttmr and xbot. https://github.com/DIYgod/RSSHub/commit/9e6f84df79bc9efcfabb0ca0768d7fded6775a1a. diygod henryqw hyoban +-ishowman impersonate as KwToPA in #22207. 72864e06b8a18d7cbef8f78d23dee3c069f8aa23 neverbehave pseudoyu tonyrl 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..863a5c8ead42 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 @@ -74,13 +74,13 @@ jobs: uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Log in to Docker Hub - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0 with: username: ${{ vars.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to the Container registry - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -88,7 +88,7 @@ jobs: - name: Extract Docker metadata (ordinary version) id: meta-ordinary - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: | ${{ vars.DOCKER_USERNAME }}/${{ steps.repo-name.outputs.repo-name }} @@ -107,7 +107,7 @@ jobs: - name: Build and push Docker image (ordinary version) id: build-and-push - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . tags: ${{ steps.image-name-ordinary.outputs.tags }} @@ -118,7 +118,7 @@ jobs: outputs: type=image,compression=zstd,force-compression=true,push-by-digest=true,name-canonical=true,push=true - name: Attest (ordinary version) - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1 with: subject-name: | ${{ vars.DOCKER_USERNAME }}/${{ steps.repo-name.outputs.repo-name }} @@ -141,7 +141,7 @@ jobs: - name: Extract Docker metadata (Chromium-bundled version) id: meta-chromium-bundled - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: | ${{ vars.DOCKER_USERNAME }}/${{ steps.repo-name.outputs.repo-name }} @@ -160,7 +160,7 @@ jobs: - name: Build and push Docker image (Chromium-bundled version) id: build-and-push-chromium - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . build-args: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=0 @@ -173,7 +173,7 @@ jobs: outputs: type=image,compression=zstd,force-compression=true,push-by-digest=true,name-canonical=true,push=true - name: Attest (Chromium-bundled version) - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1 with: subject-name: | ${{ vars.DOCKER_USERNAME }}/${{ steps.repo-name.outputs.repo-name }} @@ -207,13 +207,13 @@ jobs: uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Log in to Docker Hub - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0 with: username: ${{ vars.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to the Container registry - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -228,7 +228,7 @@ jobs: - name: Extract Docker metadata (ordinary version) id: meta-ordinary-merge - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: | ${{ vars.DOCKER_USERNAME }}/${{ needs.release.outputs.repo-name }} @@ -254,7 +254,7 @@ jobs: - name: Extract Docker metadata (Chromium-bundled version) id: meta-chromium-bundled-merge - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: | ${{ vars.DOCKER_USERNAME }}/${{ needs.release.outputs.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..9624061fb80e 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -27,20 +27,20 @@ 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 - name: Extract Docker metadata id: meta - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: rsshub flavor: latest=true - name: Build Docker image - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . build-args: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=0 # also test bundling Chromium @@ -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..09b5275b8143 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 @@ -70,7 +70,7 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, issue_number: prNumber, - body: 'This pull request has been automatically closed.', + body: 'This pull request has been automatically closed for denounced users by [vouch](https://github.com/mitchellh/vouch).' }); await github.rest.pulls.update({ owner: context.repo.owner, @@ -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..0adea99e95d7 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/* @@ -68,6 +68,7 @@ jobs: - uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} + sync-labels: true vouch-check-pr: name: Vouch check PR @@ -79,12 +80,11 @@ jobs: runs-on: ubuntu-slim timeout-minutes: 5 steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Check if PR author is denounced - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + uses: mitchellh/vouch/action/check-pr@c6d80ead49839655b61b422700b7a3bc9d0804a9 # v1.4.2 with: - script: | - const { default: checkPr } = await import('${{ github.workspace }}/scripts/workflow/vouch/check-pr.mjs') - await checkPr({ github, context, core }) + pr-number: ${{ github.event.pull_request.number }} + auto-close: true + require-vouch: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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..e8ae1cec4fac 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@73ec4cbb5e56df5591eae286972d5b2201ffe90f # v3.15.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..244cc9762e93 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,98 @@ // #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/consistent-assert": "error", + // "unicorn-js/consistent-boolean-name": "error", // unopinionated + "unicorn-js/consistent-class-member-order": "error", + "unicorn-js/consistent-compound-words": "error", + "unicorn-js/consistent-conditional-object-spread": "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-js/consistent-tuple-labels": "error", + "unicorn-js/default-export-style": "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", { "overrideGlobals": { "document": "readonly", "window": "readonly", "fetch": "readonly", "XMLHttpRequest": "readonly" } }], + "unicorn-js/logical-assignment-operators": "error", + "unicorn/max-nested-calls": "error", + // "unicorn-js/name-replacements": "error", // unopinionated, rebranded prevent-abbreviations "unicorn/new-for-builtins": "error", "unicorn/no-abusive-eslint-disable": "error", "unicorn/no-accessor-recursion": "error", + "unicorn-js/no-accidental-bitwise-operator": "error", "unicorn/no-anonymous-default-export": "error", // "unicorn/no-array-callback-reference": "error", + "unicorn-js/no-array-concat-in-loop": "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-js/no-array-sort-for-min-max": "error", + "unicorn-js/no-array-splice": "error", + "unicorn-js/no-async-promise-finally": "error", // "unicorn/no-await-expression-member": "error", "unicorn/no-await-in-promise-methods": "error", + "unicorn-js/no-blob-to-file": "error", + "unicorn-js/no-boolean-sort-comparator": "error", + // "unicorn-js/no-break-in-nested-loop": "error", // unopinionated + // "unicorn-js/no-canvas-to-image": "error", // unused + "unicorn-js/no-chained-comparison": "error", + "unicorn-js/no-collection-bracket-access": "error", + "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-constant-zero-expression": "error", + // "unicorn-js/no-declarations-before-early-exit": "error", // unopinionated "unicorn/no-document-cookie": "error", + "unicorn-js/no-double-comparison": "error", + "unicorn-js/no-duplicate-if-branches": "error", + "unicorn-js/no-duplicate-logical-operands": "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/no-hex-escape": "error", + "unicorn-js/no-global-object-property-assignment": "error", "unicorn/no-immediate-mutation": "error", + "unicorn-js/no-impossible-length-comparison": "error", + "unicorn-js/no-incorrect-query-selector": "error", + // "unicorn-js/no-incorrect-template-string-interpolation": "error", // unopinionated, too many false positives in v68 "unicorn/no-instanceof-builtins": "error", + "unicorn-js/no-invalid-argument-count": "error", + "unicorn-js/no-invalid-character-comparison": "error", "unicorn/no-invalid-fetch-options": "error", "unicorn/no-invalid-remove-event-listener": "error", + "unicorn-js/no-invalid-well-known-symbol-methods": "error", + "unicorn-js/no-late-current-target-access": "error", + "unicorn-js/no-late-event-control": "error", "unicorn/no-lonely-if": "error", + "unicorn-js/no-loop-iterable-mutation": "error", "unicorn/no-magic-array-flat-depth": "error", + "unicorn-js/no-mismatched-map-key": "error", + "unicorn-js/no-misrefactored-assignment": "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", @@ -194,107 +247,281 @@ // "unicorn/no-nested-ternary": "error", "unicorn/no-new-array": "error", "unicorn/no-new-buffer": "error", + "unicorn-js/no-non-function-verb-prefix": "error", + "unicorn-js/no-nonstandard-builtin-properties": "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-js/no-selector-as-dom-name": "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-assignment-in-function": "error", // unopinionated + // "unicorn-js/no-top-level-side-effects": "error", // allows dayjs.extend() "unicorn/no-typeof-undefined": "error", + "unicorn-js/no-uncalled-method": "error", + "unicorn-js/no-undeclared-class-members": "error", "unicorn/no-unnecessary-array-flat-depth": "error", + "unicorn-js/no-unnecessary-array-flat-map": "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-boolean-comparison": "error", + "unicorn-js/no-unnecessary-fetch-options": "error", + "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-js/no-unreadable-for-of-expression": "error", "unicorn/no-unreadable-iife": "error", + "unicorn-js/no-unreadable-object-destructuring": "error", + "unicorn-js/no-unsafe-buffer-conversion": "error", + "unicorn-js/no-unsafe-promise-all-settled-values": "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-js/no-useless-coercion": "error", "unicorn/no-useless-collection-argument": "error", + "unicorn-js/no-useless-compound-assignment": "error", + "unicorn-js/no-useless-concat": "error", + "unicorn-js/no-useless-continue": "error", + "unicorn-js/no-useless-delete-check": "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-js/no-useless-logical-operand": "error", + "unicorn-js/no-useless-override": "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-js/no-xor-as-exponentiation": "error", "unicorn/no-zero-fractions": "error", // "unicorn/number-literal-case": "error", // "unicorn/numeric-separators-style": "error", + "unicorn-js/operator-assignment": "error", + "unicorn-js/prefer-abort-signal-any": "error", + "unicorn-js/prefer-abort-signal-timeout": "error", "unicorn/prefer-add-event-listener": "error", + "unicorn-js/prefer-add-event-listener-options": "error", + "unicorn-js/prefer-aggregate-error": "error", "unicorn/prefer-array-find": "error", "unicorn/prefer-array-flat": "error", "unicorn/prefer-array-flat-map": "error", + "unicorn-js/prefer-array-from-async": "error", + "unicorn-js/prefer-array-from-map": "error", + "unicorn-js/prefer-array-from-range": "error", "unicorn/prefer-array-index-of": "error", + "unicorn-js/prefer-array-iterable-methods": "error", + "unicorn-js/prefer-array-last-methods": "error", + "unicorn-js/prefer-array-slice": "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-js/prefer-block-statement-over-iife": "error", + "unicorn-js/prefer-boolean-return": "error", "unicorn/prefer-class-fields": "error", "unicorn/prefer-classlist-toggle": "error", // "unicorn/prefer-code-point": "error", + "unicorn-js/prefer-continue": "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-js/prefer-dom-node-replace-children": "error", "unicorn/prefer-dom-node-text-content": "error", + "unicorn-js/prefer-early-return": "error", + "unicorn-js/prefer-else-if": "error", "unicorn/prefer-event-target": "error", - "unicorn-js/prefer-export-from": "error", // use jsPlugins + "unicorn/prefer-export-from": "error", + "unicorn-js/prefer-flat-math-min-max": "error", + "unicorn-js/prefer-get-or-insert-computed": "error", + "unicorn-js/prefer-global-number-constants": "error", // "unicorn/prefer-global-this": "error", + "unicorn-js/prefer-group-by": "error", + "unicorn-js/prefer-has-check": "error", + "unicorn-js/prefer-hoisting-branch-code": "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-helpers": "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-map-from-entries": "error", + "unicorn-js/prefer-math-abs": "error", + "unicorn-js/prefer-math-constants": "error", "unicorn/prefer-math-min-max": "error", "unicorn/prefer-math-trunc": "error", + // "unicorn-js/prefer-minimal-ternary": "error", // unopinionated, fixes have less readability "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/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-js/prefer-observer-apis": "error", "unicorn/prefer-optional-catch-binding": "error", + // "unicorn-js/prefer-path2d": "error", // unused + "unicorn-js/prefer-private-class-fields": "error", + "unicorn-js/prefer-promise-try": "error", + "unicorn-js/prefer-promise-with-resolvers": "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-js/prefer-set-methods": "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": "warn", + "unicorn-js/prefer-simple-condition-first": "error", + "unicorn-js/prefer-simple-sort-comparator": "error", + "unicorn-js/prefer-simplified-conditions": "error", + "unicorn-js/prefer-single-array-predicate": "error", + "unicorn/prefer-single-call": "error", + "unicorn-js/prefer-single-object-destructuring": "error", + "unicorn-js/prefer-single-replace": "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-js/prefer-toggle-attribute": "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-unary-minus": "error", + "unicorn-js/prefer-unicode-code-point-escapes": "error", + "unicorn-js/prefer-url-can-parse": "error", + "unicorn-js/prefer-url-href": "error", + "unicorn-js/prefer-url-search-parameters": "error", + "unicorn-js/prefer-while-loop-condition": "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 +551,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 +610,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 +640,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", @@ -441,7 +670,6 @@ "unicorn/no-await-expression-member": "off", "unicorn/no-empty-file": "warn", // "unicorn/no-for-loop": "off", // won't be implemented - "unicorn/no-hex-escape": "warn", "unicorn/no-nested-ternary": "off", "unicorn/no-null": "off", "unicorn/no-object-as-default-parameter": "warn", @@ -559,9 +787,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 . - -Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). +This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at [https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/). -[homepage]: https://www.contributor-covenant.org +Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy of this license, visit [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/) -For answers to common questions about this code of conduct, see the FAQ at -. Translations are available at . +For answers to common questions about Contributor Covenant, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are provided at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). Additional enforcement and community guideline resources can be found at [https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources). The enforcement ladder was inspired by the work of [Mozilla’s code of conduct team](https://github.com/mozilla/inclusion). diff --git a/Dockerfile b/Dockerfile index 6683193b7ed6..e79e017996a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:24-bookworm AS dep-builder +FROM node:24-trixie AS dep-builder # Here we use the non-slim image to provide build-time deps (compilers and python), thus no need to install later. # This effectively speeds up qemu-based cross-build. @@ -30,23 +30,23 @@ RUN \ # --------------------------------------------------------------------------------------------------------------------- -FROM debian:bookworm-slim AS dep-version-parser +FROM debian:trixie-slim AS dep-version-parser # This stage is necessary to limit the cache miss scope. # With this stage, any modification to package.json won't break the build cache of the next two stages as long as the # version unchanged. -# node:24-bookworm-slim is based on debian:bookworm-slim so this stage would not cause any additional download. +# node:24-trixie-slim is based on debian:trixie-slim so this stage would not cause any additional download. WORKDIR /ver COPY ./package.json /app/ RUN \ set -ex && \ - grep -Po '(?<="playwright": ")[^\s"]*(?=")' /app/package.json | tee /ver/.playwright_version && \ + grep -Po '(?<="patchright": ")[^\s"]*(?=")' /app/package.json | tee /ver/.patchright_version && \ grep -Po '(?<="@vercel/nft": ")[^\s"]*(?=")' /app/package.json | tee /ver/.nft_version && \ grep -Po '(?<="fs-extra": ")[^\s"]*(?=")' /app/package.json | tee /ver/.fs_extra_version # --------------------------------------------------------------------------------------------------------------------- -FROM node:24-bookworm-slim AS docker-minifier +FROM node:24-trixie-slim AS docker-minifier # The stage is used to further reduce the image size by removing unused files. WORKDIR /minifier @@ -83,17 +83,17 @@ RUN \ # --------------------------------------------------------------------------------------------------------------------- -FROM node:24-bookworm-slim AS chromium-downloader +FROM node:24-trixie-slim AS chromium-downloader # This stage is necessary to improve build concurrency and minimize the image size. # Yeah, downloading Chromium never needs those dependencies below. WORKDIR /app -COPY --from=dep-version-parser /ver/.playwright_version /app/.playwright_version +COPY --from=dep-version-parser /ver/.patchright_version /app/.patchright_version ARG TARGETPLATFORM ARG USE_CHINA_NPM_REGISTRY=0 ARG PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 -# The official recommended way to use Playwright on x86(_64) is to use the bundled browser. +# The official recommended way to use Patchright on x86(_64) is to use the bundled browser. RUN \ set -ex ; \ if [ "$PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD" = 0 ] && [ "$TARGETPLATFORM" = 'linux/amd64' ]; then \ @@ -106,16 +106,16 @@ RUN \ unset PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD && \ export PLAYWRIGHT_BROWSERS_PATH=/app/node_modules/.cache/ms-playwright && \ corepack enable pnpm && \ - pnpm --allow-build=playwright add playwright@$(cat /app/.playwright_version) --save-prod && \ + pnpm --allow-build=patchright --allow-build=patchright-core add patchright@$(cat /app/.patchright_version) --save-prod && \ pnpm rb && \ - pnpm exec playwright install chromium ; \ + pnpm exec patchright install chromium ; \ else \ mkdir -p /app/node_modules/.cache/ms-playwright ; \ fi; # --------------------------------------------------------------------------------------------------------------------- -FROM node:24-bookworm-slim AS app +FROM node:24-trixie-slim AS app LABEL org.opencontainers.image.authors="https://github.com/DIYgod/RSSHub" @@ -127,8 +127,7 @@ WORKDIR /app # install deps first to avoid cache miss or disturbing buildkit to build concurrently ARG TARGETPLATFORM ARG PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 -# https://playwright.dev/docs/docker#introduction -# https://www.debian.org/releases/bookworm/amd64/release-notes/ch-information.en.html#noteworthy-obsolete-packages +# https://playwright.dev/docs/library#browser-downloads # On arm/arm64, install Chromium from the distribution repositories. RUN \ set -ex && \ @@ -140,8 +139,8 @@ RUN \ if [ "$TARGETPLATFORM" = 'linux/amd64' ]; then \ apt-get install -yq --no-install-recommends \ ca-certificates fonts-liberation wget xdg-utils \ - libasound2 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 libcairo2 libcups2 libdbus-1-3 libdrm2 \ - libexpat1 libgbm1 libglib2.0-0 libnspr4 libnss3 libpango-1.0-0 libx11-6 libxcb1 libxcomposite1 \ + libasound2t64 libatk-bridge2.0-0t64 libatk1.0-0t64 libatspi2.0-0t64 libcairo2 libcups2t64 libdbus-1-3 libdrm2 \ + libexpat1 libgbm1 libglib2.0-0t64 libnspr4 libnss3 libpango-1.0-0 libx11-6 libxcb1 libxcomposite1 \ libxdamage1 libxext6 libxfixes3 libxkbcommon0 libxrandr2 \ ; \ else \ diff --git a/eslint-plugins/no-then.js b/eslint-plugins/no-then.js deleted file mode 100644 index eef945cc34c9..000000000000 --- a/eslint-plugins/no-then.js +++ /dev/null @@ -1,45 +0,0 @@ -import { eslintCompatPlugin } from '@oxlint/plugins'; - -const rule = { - meta: { - type: 'suggestion', - docs: { - description: 'enforce using `async/await` syntax over Promises', - url: 'https://github.com/github/eslint-plugin-github/blob/main/docs/rules/no-then.md', - recommended: true, - }, - schema: [], - messages: { - preferAsyncAwait: 'Prefer async/await to Promise.{{method}}()', - }, - }, - - createOnce(context) { - return { - MemberExpression(node) { - if (node.property && node.property.name === 'then') { - context.report({ - node: node.property, - messageId: 'preferAsyncAwait', - data: { method: 'then' }, - }); - } else if (node.property && node.property.name === 'catch') { - context.report({ - node: node.property, - messageId: 'preferAsyncAwait', - data: { method: 'catch' }, - }); - } - }, - }; - }, -}; - -export default eslintCompatPlugin({ - meta: { - name: 'github', - }, - rules: { - 'no-then': rule, - }, -}); diff --git a/eslint-plugins/nsfw-flag.js b/eslint-plugins/nsfw-flag.js index 32c9397cd690..a4cbc5eb19fa 100644 --- a/eslint-plugins/nsfw-flag.js +++ b/eslint-plugins/nsfw-flag.js @@ -119,96 +119,96 @@ export default eslintCompatPlugin({ missingNsfwFlag: 'NSFW route is missing the nsfw flag in features', }, }, - createOnce(context) { - return { - before() { - // 如果不是 NSFW 路由,跳过检查 - if (!isNsfwRoute(context.filename)) { - return false; - } - }, - ExportNamedDeclaration(node) { - // 查找 export const route: Route = {...} - if ( - node.declaration && - node.declaration.type === 'VariableDeclaration' && - node.declaration.declarations && - node.declaration.declarations[0] && - node.declaration.declarations[0].id && - node.declaration.declarations[0].id.name === 'route' - ) { - const routeDeclaration = node.declaration.declarations[0]; - const routeObject = routeDeclaration.init; + createOnce: (context) => ({ + before() { + // 如果不是 NSFW 路由,跳过检查 + if (!isNsfwRoute(context.filename)) { + return false; + } + }, + ExportNamedDeclaration(node) { + // 查找 export const route: Route = {...} + if ( + !node.declaration || + node.declaration.type !== 'VariableDeclaration' || + !node.declaration.declarations || + !node.declaration.declarations[0] || + !node.declaration.declarations[0].id || + node.declaration.declarations[0].id.name !== 'route' + ) { + return; + } + const routeDeclaration = node.declaration.declarations[0]; + const routeObject = routeDeclaration.init; + + if (routeObject && routeObject.type === 'ObjectExpression') { + let featuresProperty = null; + let nsfwProperty = null; - if (routeObject && routeObject.type === 'ObjectExpression') { - let featuresProperty = null; - let nsfwProperty = null; + // 查找 features 属性 + for (const prop of routeObject.properties) { + if (!(prop.type === 'Property' && prop.key && prop.key.name === 'features')) { + continue; + } - // 查找 features 属性 - for (const prop of routeObject.properties) { - if (prop.type === 'Property' && prop.key && prop.key.name === 'features') { - featuresProperty = prop; + featuresProperty = prop; - // 在 features 中查找 nsfw 属性 - if (prop.value && prop.value.type === 'ObjectExpression') { - for (const featureProp of prop.value.properties) { - if (featureProp.type === 'Property' && featureProp.key && featureProp.key.name === 'nsfw') { - nsfwProperty = featureProp; - break; - } - } - } - break; + // 在 features 中查找 nsfw 属性 + if (prop.value && prop.value.type === 'ObjectExpression') { + for (const featureProp of prop.value.properties) { + if (!(featureProp.type === 'Property' && featureProp.key && featureProp.key.name === 'nsfw')) { + continue; } - } - - // 检查是否需要添加或修复 nsfw 标志 - if (!featuresProperty) { - // 没有 features 属性,需要添加整个 features 对象 - context.report({ - node: routeObject, - messageId: 'missingNsfwFlag', - fix(fixer) { - // 在对象的最后添加 features 属性 - const lastProperty = routeObject.properties.at(-1); - return lastProperty - ? fixer.insertTextAfter(lastProperty, ',\n features: {\n nsfw: true,\n }') - : // 空对象的情况 - fixer.insertTextAfter(routeObject.properties.length > 0 ? routeObject.properties.at(-1) : routeObject, '\n features: {\n nsfw: true,\n }\n'); - }, - }); - } else if (!nsfwProperty) { - // 有 features 属性但没有 nsfw 属性 - context.report({ - node: featuresProperty.value, - messageId: 'missingNsfwFlag', - fix(fixer) { - const featuresObject = featuresProperty.value; - if (featuresObject.properties.length > 0) { - const lastFeatureProp = featuresObject.properties.at(-1); - return fixer.insertTextAfter(lastFeatureProp, ',\n nsfw: true'); - } else { - // features 是空对象 - return fixer.replaceTextRange([featuresObject.range[0] + 1, featuresObject.range[1] - 1], '\n nsfw: true,\n '); - } - }, - }); - } else if (nsfwProperty.value && (nsfwProperty.value.type !== 'Literal' || nsfwProperty.value.value !== true)) { - // nsfw 属性存在但不是 true - context.report({ - node: nsfwProperty.value, - messageId: 'missingNsfwFlag', - fix(fixer) { - return fixer.replaceText(nsfwProperty.value, 'true'); - }, - }); + nsfwProperty = featureProp; + break; } } + break; } - }, - }; - }, + + // 检查是否需要添加或修复 nsfw 标志 + if (!featuresProperty) { + // 没有 features 属性,需要添加整个 features 对象 + context.report({ + node: routeObject, + messageId: 'missingNsfwFlag', + fix(fixer) { + // 在对象的最后添加 features 属性 + const lastProperty = routeObject.properties.at(-1); + + return lastProperty + ? fixer.insertTextAfter(lastProperty, ',\n features: {\n nsfw: true,\n }') + : // 空对象的情况 + fixer.insertTextAfter(routeObject.properties.length > 0 ? routeObject.properties.at(-1) : routeObject, '\n features: {\n nsfw: true,\n }\n'); + }, + }); + } else if (!nsfwProperty) { + // 有 features 属性但没有 nsfw 属性 + context.report({ + node: featuresProperty.value, + messageId: 'missingNsfwFlag', + fix(fixer) { + const featuresObject = featuresProperty.value; + if (featuresObject.properties.length > 0) { + const lastFeatureProp = featuresObject.properties.at(-1); + return fixer.insertTextAfter(lastFeatureProp, ',\n nsfw: true'); + } + // features 是空对象 + return fixer.replaceTextRange([featuresObject.range[0] + 1, featuresObject.range[1] - 1], '\n nsfw: true,\n '); + }, + }); + } else if (nsfwProperty.value && (nsfwProperty.value.type !== 'Literal' || nsfwProperty.value.value !== true)) { + // nsfw 属性存在但不是 true + context.report({ + node: nsfwProperty.value, + messageId: 'missingNsfwFlag', + fix: (fixer) => fixer.replaceText(nsfwProperty.value, 'true'), + }); + } + } + }, + }), }, }, }); diff --git a/flake.lock b/flake.lock index cef7e05c4f14..ad78f8f6f42b 100644 --- a/flake.lock +++ b/flake.lock @@ -64,11 +64,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1780316214, - "narHash": "sha256-X3EG0oxt03MegwC/MnQv1saoq9nphEGSSGEAj8mZQOg=", + "lastModified": 1783049965, + "narHash": "sha256-z2QvrvE+gzLnpqfLpQA4aq2gAB4xn6C5q2o1l7GBMXU=", "owner": "cachix", "repo": "devenv", - "rev": "d5e9138bae90fe199fbe5de7675014d76d28873b", + "rev": "afed7bf3240c70d2827569568d2c46c25a64db51", "type": "github" }, "original": { @@ -135,11 +135,11 @@ "ghostty": { "flake": false, "locked": { - "lastModified": 1779069789, - "narHash": "sha256-ojo+gso45/6CVSuqfSVnlWpQ4d0QeLgwok+v/g3yu0E=", + "lastModified": 1782866021, + "narHash": "sha256-BOLtzL5iAHmtCOg9/DXtcfw+K86QWol088K72chJB04=", "owner": "ghostty-org", "repo": "ghostty", - "rev": "4b7bf0b20e3baf9c1ba10c63f2ad1fd853faea8f", + "rev": "e1d31deaaed21aa9225afca78d778fb373c95852", "type": "github" }, "original": { @@ -154,18 +154,17 @@ "devenv", "flake-compat" ], - "gitignore": "gitignore", "nixpkgs": [ "devenv", "nixpkgs" ] }, "locked": { - "lastModified": 1778507602, - "narHash": "sha256-kTwur1wV+01SdqskVMSo6JMEpg71ps3HpbFY2GsflKs=", + "lastModified": 1782908218, + "narHash": "sha256-wLMOrPgVyeF3XmP+qfYcLqnVdTxikdcSvbIY7rA9jTA=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "61ab0e80d9c7ab14c256b5b453d8b3fb0189ba0a", + "rev": "9f7e99119ece7705299595299f3b031f39356de1", "type": "github" }, "original": { @@ -174,28 +173,6 @@ "type": "github" } }, - "gitignore": { - "inputs": { - "nixpkgs": [ - "devenv", - "git-hooks", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1709087332, - "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", - "type": "github" - } - }, "nix": { "inputs": { "flake-compat": [ @@ -222,11 +199,11 @@ ] }, "locked": { - "lastModified": 1779748925, - "narHash": "sha256-meIhqGC04O5VXbKSFXSQoOKp+XCq5RMnwAk1Guo0VQo=", + "lastModified": 1782407171, + "narHash": "sha256-xem+4ncdQCTFJsQ4PrVuyVmi3j4w/Yqg298hBUzVejA=", "owner": "cachix", "repo": "nix", - "rev": "0bc443c8ff235c3547d09327b48aaa2ab98b15f2", + "rev": "782ac1b155679b065ec945ae50d0fa1d495883b7", "type": "github" }, "original": { @@ -249,11 +226,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1778381404, - "narHash": "sha256-FqhdOTA8vyoIpkHhbs2cCT7h6EWM7nsLeOYJc1ifQLE=", + "lastModified": 1780381423, + "narHash": "sha256-S1BIJiQF4lRtJUKak0e97JwNvbe69/LW78YJJuB18Qk=", "owner": "nix-community", "repo": "nixd", - "rev": "e3e45eb76663f522e196b7f0cf34cab201db7779", + "rev": "0e07c08c448a2995e7793d1098437b29bbe80b02", "type": "github" }, "original": { @@ -267,11 +244,11 @@ "nixpkgs-src": "nixpkgs-src" }, "locked": { - "lastModified": 1778507786, - "narHash": "sha256-HzSQCKMsMr8r55LwM1JuzIOB+8bzk0FEv6sItKvsfoY=", + "lastModified": 1782132010, + "narHash": "sha256-ZnAVHdVrotp80iIMm5CSR1fdxPlw7Uwmwxb+O/wsgZ8=", "owner": "cachix", "repo": "devenv-nixpkgs", - "rev": "8f24a228a782e24576b155d1e39f0d914b380691", + "rev": "12866ae2dddbc0ab8b329915f8072bb9c75bde89", "type": "github" }, "original": { @@ -284,11 +261,11 @@ "nixpkgs-src": { "flake": false, "locked": { - "lastModified": 1778274207, - "narHash": "sha256-I4puXmX1iovcCHZlRmztO3vW0mAbbRvq4F8wgIMQ1MM=", + "lastModified": 1781607440, + "narHash": "sha256-rxO+uc/KFbSJp+pgyXRuAX6QlG9hJdnt0BXpEQRXY+U=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b3da656039dc7a6240f27b2ef8cc6a3ef3bccae7", + "rev": "3e41b24abd260e8f71dbe2f5737d24122f972158", "type": "github" }, "original": { @@ -300,11 +277,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1780243769, - "narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=", + "lastModified": 1782723713, + "narHash": "sha256-oPXCU/SSUokcGaJREHibG1CBX3+s/W7orDWQOZDsEeQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "331800de5053fcebacf6813adb5db9c9dca22a0c", + "rev": "b5aa0fbd538984f6e3d201be0005b4463d8b09f8", "type": "github" }, "original": { @@ -329,11 +306,11 @@ ] }, "locked": { - "lastModified": 1779074409, - "narHash": "sha256-6aXy8Ga41iLVM8ibddFU1O5+wYWcBGNEfZzZuL91eIc=", + "lastModified": 1782875958, + "narHash": "sha256-5eqDcnBjb1424HRQdnhuhNOBZguq1Z2tqSa2OMF/m2c=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "2a77b5b1dc952f214e8102acdef1622b68515560", + "rev": "13139aefa973f3d96c60c0fbab801de058ae25ca", "type": "github" }, "original": { @@ -366,11 +343,11 @@ ] }, "locked": { - "lastModified": 1775636079, - "narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=", + "lastModified": 1780220602, + "narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba", + "rev": "db947814a175b7ca6ded66e21383d938df01c227", "type": "github" }, "original": { diff --git a/lib/api/category/one.ts b/lib/api/category/one.ts index a8c8726cb9d2..60fb290c39b7 100644 --- a/lib/api/category/one.ts +++ b/lib/api/category/one.ts @@ -7,19 +7,21 @@ const categoryList: Record = {}; for (const namespace in namespaces) { for (const path in namespaces[namespace].routes) { - if (namespaces[namespace].routes[path].categories?.length) { - for (const category of namespaces[namespace].routes[path].categories!) { - if (!categoryList[category]) { - categoryList[category] = {}; - } - if (!categoryList[category][namespace]) { - categoryList[category][namespace] = { - ...namespaces[namespace], - routes: {}, - }; - } - categoryList[category][namespace].routes[path] = namespaces[namespace].routes[path]; + if (!namespaces[namespace].routes[path].categories?.length) { + continue; + } + const categories = namespaces[namespace].routes[path].categories!; + for (const category of categories) { + if (!Object.hasOwn(categoryList, category)) { + categoryList[category] = {}; + } + if (!Object.hasOwn(categoryList[category], namespace)) { + categoryList[category][namespace] = { + ...namespaces[namespace], + routes: {}, + }; } + categoryList[category][namespace].routes[path] = namespaces[namespace].routes[path]; } } } diff --git a/lib/api/index.ts b/lib/api/index.ts index 76ba26e49ddd..9ff609b4b234 100644 --- a/lib/api/index.ts +++ b/lib/api/index.ts @@ -5,7 +5,7 @@ import { Scalar } from '@scalar/hono-api-reference'; import { handler as categoryOneHandler, route as categoryOneRoute } from '@/api/category/one'; import { handler as followConfigHandler, route as followConfigRoute } from '@/api/follow/config'; import { handler as namespaceAllHandler, route as namespaceAllRoute } from '@/api/namespace/all'; -import { handler as namespaceOneHandler, route as namespaceOneRoute } from '@/api/namespace/one'; +import { handler as namespaceOneHandler, route as namespaceOneRoute, routeNested as namespaceOneNestedRoute } from '@/api/namespace/one'; import { handler as radarRulesAllHandler, route as radarRulesAllRoute } from '@/api/radar/rules/all'; import { handler as radarRulesOneHandler, route as radarRulesOneRoute } from '@/api/radar/rules/one'; import { handler as routeStatusHandler, route as routeStatusRoute } from '@/api/route/status'; @@ -14,6 +14,7 @@ const app = new OpenAPIHono(); app.openapi(namespaceAllRoute, namespaceAllHandler); app.openapi(namespaceOneRoute, namespaceOneHandler); +app.openapi(namespaceOneNestedRoute, namespaceOneHandler); app.openapi(radarRulesAllRoute, radarRulesAllHandler); app.openapi(radarRulesOneRoute, radarRulesOneHandler); app.openapi(categoryOneRoute, categoryOneHandler); diff --git a/lib/api/namespace.test.ts b/lib/api/namespace.test.ts index 960e87e1ff8a..d1d318ab2bd5 100644 --- a/lib/api/namespace.test.ts +++ b/lib/api/namespace.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; +import api from '@/api'; import { handler as allHandler } from '@/api/namespace/all'; import { handler as oneHandler } from '@/api/namespace/one'; import { namespaces } from '@/registry'; @@ -13,6 +14,8 @@ const createCtx = (param: Record = {}) => }) as any; describe('api/namespace', () => { + const nestedKey = Object.keys(namespaces).find((key) => key.includes('/')) as string; + it('returns all namespaces', () => { const result = allHandler(createCtx()); expect(result).toBe(namespaces); @@ -22,4 +25,25 @@ describe('api/namespace', () => { const result = oneHandler(createCtx({ namespace: 'test' })); expect(result).toBe(namespaces.test); }); + + it('returns a nested namespace', () => { + expect(nestedKey).toBeDefined(); + const [namespace, sub] = nestedKey.split('/'); + const result = oneHandler(createCtx({ namespace, sub })); + expect(result).toBe(namespaces[nestedKey]); + }); + + it('serves a single namespace over HTTP', async () => { + expect(namespaces.github).toBeDefined(); + const response = await api.request('/namespace/github'); + expect(response.status).toBe(200); + expect(await response.json()).toEqual(namespaces.github); + }); + + it('serves a nested namespace over HTTP', async () => { + expect(nestedKey).toBeDefined(); + const response = await api.request(`/namespace/${nestedKey}`); + expect(response.status).toBe(200); + expect(await response.json()).toEqual(namespaces[nestedKey]); + }); }); diff --git a/lib/api/namespace/one.ts b/lib/api/namespace/one.ts index d447e5805802..9c9252a97a1c 100644 --- a/lib/api/namespace/one.ts +++ b/lib/api/namespace/one.ts @@ -3,15 +3,18 @@ import { createRoute, z } from '@hono/zod-openapi'; import { namespaces } from '@/registry'; -const ParamsSchema = z.object({ - namespace: z.string().openapi({ +const pathParam = (name: string, example: string) => + z.string().openapi({ param: { - name: 'namespace', + name, in: 'path', }, - example: 'github', - }), -}); + example, + }); + +const nestedExample = Object.keys(namespaces) + .find((key) => key.includes('/')) + ?.split('/') ?? ['namespace', 'sub']; const route = createRoute({ method: 'get', @@ -19,7 +22,7 @@ const route = createRoute({ description: 'Information about a namespace', tags: ['Namespace'], request: { - params: ParamsSchema, + params: z.object({ namespace: pathParam('namespace', 'github') }), }, responses: { 200: { @@ -28,9 +31,24 @@ const route = createRoute({ }, }); +const routeNested = createRoute({ + method: 'get', + path: '/namespace/{namespace}/{sub}', + description: `Information about a nested namespace (e.g. ${nestedExample.join('/')})`, + tags: ['Namespace'], + request: { + params: z.object({ namespace: pathParam('namespace', nestedExample[0]), sub: pathParam('sub', nestedExample[1]) }), + }, + responses: { + 200: { + description: 'Namespace registry data for a nested namespace', + }, + }, +}); + const handler: RouteHandler = (ctx) => { - const { namespace } = ctx.req.valid('param'); - return ctx.json(namespaces[namespace]); + const { namespace, sub } = ctx.req.valid('param') as { namespace: string; sub?: string }; + return ctx.json(namespaces[[namespace, sub].filter(Boolean).join('/')]); }; -export { handler, route }; +export { handler, route, routeNested }; diff --git a/lib/api/radar/rules/all.ts b/lib/api/radar/rules/all.ts index d94e925619db..e8136063a787 100644 --- a/lib/api/radar/rules/all.ts +++ b/lib/api/radar/rules/all.ts @@ -19,12 +19,12 @@ for (const namespace in namespaces) { const subdomain = parsedDomain.subdomain || '.'; const domain = parsedDomain.domain; if (domain) { - if (!radar[domain]) { + if (!Object.hasOwn(radar, domain)) { radar[domain] = { _name: namespaces[namespace].name, } as RadarDomain; } - if (!radar[domain][subdomain]) { + if (!Object.hasOwn(radar[domain], subdomain)) { radar[domain][subdomain] = []; } radar[domain][subdomain].push({ diff --git a/lib/api/radar/rules/one.ts b/lib/api/radar/rules/one.ts index 551f3a696ec0..1a28f117b85e 100644 --- a/lib/api/radar/rules/one.ts +++ b/lib/api/radar/rules/one.ts @@ -19,12 +19,12 @@ for (const namespace in namespaces) { const subdomain = parsedDomain.subdomain || '.'; const domain = parsedDomain.domain; if (domain) { - if (!radar[domain]) { + if (!Object.hasOwn(radar, domain)) { radar[domain] = { _name: namespaces[namespace].name, } as RadarDomain; } - if (!radar[domain][subdomain]) { + if (!Object.hasOwn(radar[domain], subdomain)) { radar[domain][subdomain] = []; } radar[domain][subdomain].push({ diff --git a/lib/bilibili-video-route.test.ts b/lib/bilibili-video-route.test.ts index 09f601237bd5..39dd7ea1b034 100644 --- a/lib/bilibili-video-route.test.ts +++ b/lib/bilibili-video-route.test.ts @@ -11,15 +11,13 @@ const destroy = vi.fn(); const getPlaywrightPage = vi.fn(); const goto = vi.fn(); const on = vi.fn(); -const setCookie = vi.fn(); -const setRequestInterception = vi.fn(); +const pageRoute = vi.fn(); const waitForResponse = vi.fn(); const page = { goto, on, - setCookie, - setRequestInterception, + route: pageRoute, waitForResponse, }; @@ -66,8 +64,7 @@ describe('/bilibili/user/video/:uid', () => { getPlaywrightPage.mockReset(); goto.mockReset(); on.mockReset(); - setCookie.mockReset(); - setRequestInterception.mockReset(); + pageRoute.mockReset(); waitForResponse.mockReset(); }); @@ -109,6 +106,7 @@ describe('/bilibili/user/video/:uid', () => { getPlaywrightPage.mockImplementation(async (_url, options) => { await options.onBeforeLoad?.(page); return { + context: {}, destroy, page, }; @@ -171,6 +169,7 @@ describe('/bilibili/user/video/:uid', () => { getPlaywrightPage.mockImplementation(async (_url, options) => { await options.onBeforeLoad?.(page); return { + context: {}, destroy, page, }; diff --git a/lib/config.ts b/lib/config.ts index a460f33723b0..e19ab2b508cb 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -10,6 +10,7 @@ type ConfigEnvKeys = | 'NODE_NAME' | 'PLAYWRIGHT_WS_ENDPOINT' | 'PUPPETEER_WS_ENDPOINT' + | 'PLAYWRIGHT_CDP_ENDPOINT' | 'CHROMIUM_EXECUTABLE_PATH' // Network | 'PORT' @@ -260,6 +261,7 @@ export type Config = { isPackage: boolean; nodeName?: string; playwrightWSEndpoint?: string; + playwrightCDPEndpoint?: string; chromiumExecutablePath?: string; // network connect: { @@ -716,9 +718,8 @@ const TRUE_UA = 'RSSHub/1.0 (+http://github.com/DIYgod/RSSHub; like FeedFetcher- const toBoolean = (value: string | undefined, defaultValue: boolean) => { if (value === undefined) { return defaultValue; - } else { - return value === '' || value === '0' || value === 'false' ? false : !!value; } + return ['', '0', 'false'].includes(value) ? false : !!value; }; const toInt = (value: string | undefined, defaultValue?: number) => (value === undefined ? defaultValue : Number.parseInt(value)); @@ -756,6 +757,7 @@ const calculateValue = () => { isPackage: !!envs.IS_PACKAGE, nodeName: envs.NODE_NAME, playwrightWSEndpoint: envs.PLAYWRIGHT_WS_ENDPOINT ?? envs.PUPPETEER_WS_ENDPOINT, + playwrightCDPEndpoint: envs.PLAYWRIGHT_CDP_ENDPOINT, chromiumExecutablePath: envs.CHROMIUM_EXECUTABLE_PATH, // network connect: { @@ -1217,24 +1219,25 @@ const calculateValue = () => { }; calculateValue(); (async () => { - if (envs.REMOTE_CONFIG) { - const { default: logger } = await import('@/utils/logger'); - try { - const data = await ofetch(envs.REMOTE_CONFIG, { - headers: { - Authorization: `Basic ${envs.REMOTE_CONFIG_AUTH}`, - }, - }); - if (data) { - envs = Object.assign(envs, data); - calculateValue(); - logger.info('Remote config loaded.'); - } else { - logger.error('Remote config load failed.'); - } - } catch (error) { - logger.error('Remote config load failed.', error); + if (!envs.REMOTE_CONFIG) { + return; + } + const { default: logger } = await import('@/utils/logger'); + try { + const data = await ofetch(envs.REMOTE_CONFIG, { + headers: { + Authorization: `Basic ${envs.REMOTE_CONFIG_AUTH}`, + }, + }); + if (data) { + envs = Object.assign(envs, data); + calculateValue(); + logger.info('Remote config loaded.'); + } else { + logger.error('Remote config load failed.'); } + } catch (error) { + logger.error('Remote config load failed.', error); } })(); diff --git a/lib/errors/index.test.ts b/lib/errors/index.test.ts index 9c102b973600..397d227cb769 100644 --- a/lib/errors/index.test.ts +++ b/lib/errors/index.test.ts @@ -25,7 +25,7 @@ describe('httperror', () => { describe('RequestInProgressError', () => { it('RequestInProgressError with retry', async () => { const responses = await Promise.all([app.request('/test/slow'), app.request('/test/slow')]); - expect(new Set(responses.map((r) => r.status))).toEqual(new Set([200, 200])); + expect(responses.map((r) => r.status)).toEqual([200, 200]); }); it('RequestInProgressError', async () => { const responses = await Promise.all([app.request('/test/slow4'), app.request('/test/slow4')]); diff --git a/lib/errors/index.tsx b/lib/errors/index.tsx index b78603815c00..5ad01a336235 100644 --- a/lib/errors/index.tsx +++ b/lib/errors/index.tsx @@ -1,5 +1,4 @@ import Honeybadger from '@honeybadger-io/js'; -import * as Sentry from '@sentry/node'; import type { ErrorHandler, NotFoundHandler } from 'hono'; import { routePath } from 'hono/route'; @@ -11,6 +10,8 @@ import Error from '@/views/error'; import NotFoundError from './types/not-found'; +const Sentry = config.sentry.dsn ? await import('@sentry/node') : undefined; + export const errorHandler: ErrorHandler = (error, ctx) => { const requestPath = ctx.req.path; const matchedRoute = routePath(ctx); @@ -26,12 +27,14 @@ export const errorHandler: ErrorHandler = (error, ctx) => { } debug.error++; - if (!debug.errorPaths[requestPath]) { + const errorPathCount = debug.errorPaths[requestPath]; + if (!errorPathCount) { debug.errorPaths[requestPath] = 0; } debug.errorPaths[requestPath]++; - if (!debug.errorRoutes[matchedRoute] && hasMatchedRoute) { + const errorRouteCount = debug.errorRoutes[matchedRoute]; + if (!errorRouteCount && hasMatchedRoute) { debug.errorRoutes[matchedRoute] = 0; } hasMatchedRoute && debug.errorRoutes[matchedRoute]++; @@ -39,13 +42,13 @@ export const errorHandler: ErrorHandler = (error, ctx) => { if (config.honeybadger.apiKey) { Honeybadger.notify(error, { - context: { name: requestPath.split('/')[1] }, + context: { name: requestPath.split('/', 2)[1] }, }); } - if (config.sentry.dsn) { + if (Sentry) { Sentry.withScope((scope) => { - scope.setTag('name', requestPath.split('/')[1]); + scope.setTag('name', requestPath.split('/', 2)[1]); Sentry.captureException(error); }); } diff --git a/lib/errors/types/captcha.ts b/lib/errors/types/captcha.ts index 46bc07e155d6..4df270d48ff2 100644 --- a/lib/errors/types/captcha.ts +++ b/lib/errors/types/captcha.ts @@ -1,5 +1,3 @@ -class CaptchaError extends Error { +export default class CaptchaError extends Error { name = 'CaptchaError'; } - -export default CaptchaError; diff --git a/lib/errors/types/config-not-found.ts b/lib/errors/types/config-not-found.ts index c96cea03c2b8..b8a3d2e14a3e 100644 --- a/lib/errors/types/config-not-found.ts +++ b/lib/errors/types/config-not-found.ts @@ -1,5 +1,3 @@ -class ConfigNotFoundError extends Error { +export default class ConfigNotFoundError extends Error { name = 'ConfigNotFoundError'; } - -export default ConfigNotFoundError; diff --git a/lib/errors/types/invalid-parameter.ts b/lib/errors/types/invalid-parameter.ts index 8599ec4d2af4..482a65e809fb 100644 --- a/lib/errors/types/invalid-parameter.ts +++ b/lib/errors/types/invalid-parameter.ts @@ -1,5 +1,3 @@ -class InvalidParameterError extends Error { +export default class InvalidParameterError extends Error { name = 'InvalidParameterError'; } - -export default InvalidParameterError; diff --git a/lib/errors/types/not-found.ts b/lib/errors/types/not-found.ts index 9cba16b31e79..2c4687daca45 100644 --- a/lib/errors/types/not-found.ts +++ b/lib/errors/types/not-found.ts @@ -1,5 +1,3 @@ -class NotFoundError extends Error { +export default class NotFoundError extends Error { name = 'NotFoundError'; } - -export default NotFoundError; diff --git a/lib/errors/types/reject.ts b/lib/errors/types/reject.ts index b6b91fe4967c..3e350672f665 100644 --- a/lib/errors/types/reject.ts +++ b/lib/errors/types/reject.ts @@ -1,5 +1,3 @@ -class RejectError extends Error { +export default class RejectError extends Error { name = 'RejectError'; } - -export default RejectError; diff --git a/lib/errors/types/request-in-progress.ts b/lib/errors/types/request-in-progress.ts index 73ae4b5705d3..aceb4bb08ec1 100644 --- a/lib/errors/types/request-in-progress.ts +++ b/lib/errors/types/request-in-progress.ts @@ -1,5 +1,3 @@ -class RequestInProgressError extends Error { +export default class RequestInProgressError extends Error { name = 'RequestInProgressError'; } - -export default RequestInProgressError; diff --git a/lib/middleware/access-control.ts b/lib/middleware/access-control.ts index 01cbbd5f7b8c..969308227698 100644 --- a/lib/middleware/access-control.ts +++ b/lib/middleware/access-control.ts @@ -13,7 +13,7 @@ const middleware: MiddlewareHandler = async (ctx, next) => { const accessKey = ctx.req.query('key'); const accessCode = ctx.req.query('code'); - if (requestPath === '/' || requestPath === '/robots.txt' || requestPath === '/favicon.ico' || requestPath === '/logo.png') { + if (['/', '/robots.txt', '/favicon.ico', '/logo.png'].includes(requestPath)) { await next(); } else { if (config.accessKey && !(config.accessKey === accessKey || accessCode === md5(requestPath + config.accessKey))) { diff --git a/lib/middleware/anti-hotlink.test.ts b/lib/middleware/anti-hotlink.test.ts index d718fad4cd19..e407fa71cd1d 100644 --- a/lib/middleware/anti-hotlink.test.ts +++ b/lib/middleware/anti-hotlink.test.ts @@ -272,7 +272,7 @@ const testAntiHotlink = async (path, expectObj, query?: string | Record `${key}=${value}`) .join('&'); } - path = path + (queryStr ? `?${queryStr}` : ''); + path += queryStr ? `?${queryStr}` : ''; const response = await app.request(path); const parsed = await parser.parseString(await response.text()); diff --git a/lib/middleware/anti-hotlink.ts b/lib/middleware/anti-hotlink.ts index 49a849570b04..7dd6e897661d 100644 --- a/lib/middleware/anti-hotlink.ts +++ b/lib/middleware/anti-hotlink.ts @@ -6,7 +6,7 @@ import { config } from '@/config'; import type { Data } from '@/types'; import logger from '@/utils/logger'; -const templateRegex = /\${([^{}]+)}/g; +const templateRegex = /\$\{([^{}]+)\}/g; const allowedUrlProperties = new Set(['hash', 'host', 'hostname', 'href', 'origin', 'password', 'pathname', 'port', 'protocol', 'search', 'searchParams', 'username']); // match path or sub-path @@ -150,7 +150,7 @@ const middleware: MiddlewareHandler = async (ctx, next) => { if (item.enclosure_url && item.enclosure_type) { if (item.enclosure_type.startsWith('image/')) { item.enclosure_url = replaceUrl(imageHotlinkTemplate, item.enclosure_url); - } else if (/^(video|audio)\//.test(item.enclosure_type)) { + } else if (/^(?:video|audio)\//.test(item.enclosure_type)) { item.enclosure_url = replaceUrl(multimediaHotlinkTemplate, item.enclosure_url); } } diff --git a/lib/middleware/debug.ts b/lib/middleware/debug.ts index 9d779728a855..5aca41000dc9 100644 --- a/lib/middleware/debug.ts +++ b/lib/middleware/debug.ts @@ -6,7 +6,8 @@ import { getDebugInfo, setDebugInfo } from '@/utils/debug-info'; const middleware: MiddlewareHandler = async (ctx, next) => { { const debug = getDebugInfo(); - if (!debug.paths[ctx.req.path]) { + const pathCount = debug.paths[ctx.req.path]; + if (!pathCount) { debug.paths[ctx.req.path] = 0; } debug.paths[ctx.req.path]++; @@ -21,7 +22,8 @@ const middleware: MiddlewareHandler = async (ctx, next) => { const debug = getDebugInfo(); const rPath = routePath(ctx); const hasMatchedRoute = rPath !== '/*'; - if (!debug.routes[rPath] && hasMatchedRoute) { + const routeCount = debug.routes[rPath]; + if (!routeCount && hasMatchedRoute) { debug.routes[rPath] = 0; } hasMatchedRoute && debug.routes[rPath]++; diff --git a/lib/middleware/logger.ts b/lib/middleware/logger.ts index 19b038aa3da3..64c77e817b5c 100644 --- a/lib/middleware/logger.ts +++ b/lib/middleware/logger.ts @@ -12,13 +12,13 @@ enum LogPrefix { const colorStatus = (status: number) => { const out: { [key: string]: string } = { - 7: `\u001B[35m${status}\u001B[0m`, - 5: `\u001B[31m${status}\u001B[0m`, - 4: `\u001B[33m${status}\u001B[0m`, - 3: `\u001B[36m${status}\u001B[0m`, - 2: `\u001B[32m${status}\u001B[0m`, - 1: `\u001B[32m${status}\u001B[0m`, - 0: `\u001B[33m${status}\u001B[0m`, + 7: `\u{1B}[35m${status}\u{1B}[0m`, + 5: `\u{1B}[31m${status}\u{1B}[0m`, + 4: `\u{1B}[33m${status}\u{1B}[0m`, + 3: `\u{1B}[36m${status}\u{1B}[0m`, + 2: `\u{1B}[32m${status}\u{1B}[0m`, + 1: `\u{1B}[32m${status}\u{1B}[0m`, + 0: `\u{1B}[33m${status}\u{1B}[0m`, }; const calculateStatus = Math.trunc(status / 100); diff --git a/lib/middleware/parameter-re2.test.ts b/lib/middleware/parameter-re2.test.ts index 7cbabd69dd7c..546ab1c636d2 100644 --- a/lib/middleware/parameter-re2.test.ts +++ b/lib/middleware/parameter-re2.test.ts @@ -2,16 +2,17 @@ import { describe, expect, it, vi } from 'vitest'; class FakeRE2 { static CASE_INSENSITIVE = 1; + + static compile(pattern: string) { + return new FakeRE2(pattern); + } + private pattern: string; constructor(pattern: string) { this.pattern = pattern; } - static compile(pattern: string) { - return new FakeRE2(pattern); - } - matcher(text: string) { return { find: () => text.includes(this.pattern), diff --git a/lib/middleware/parameter.ts b/lib/middleware/parameter.ts index 07e70e098005..ce7780cf2454 100644 --- a/lib/middleware/parameter.ts +++ b/lib/middleware/parameter.ts @@ -74,11 +74,11 @@ const middleware: MiddlewareHandler = async (ctx, next) => { } // fix allowEmpty - data.item = data.item || []; + data.item ||= []; // decode HTML entities - data.title && (data.title = entities.decodeXML(data.title + '')); - data.description && (data.description = entities.decodeXML(data.description + '')); + data.title &&= entities.decodeXML(data.title + ''); + data.description &&= entities.decodeXML(data.description + ''); // sort items if (ctx.req.query('sorted') !== 'false') { @@ -86,7 +86,7 @@ const middleware: MiddlewareHandler = async (ctx, next) => { } const handleItem = (item: DataItem) => { - item.title && (item.title = entities.decodeXML(item.title + '')); + item.title &&= entities.decodeXML(item.title + ''); // handle pubDate if (item.pubDate) { @@ -227,15 +227,15 @@ const middleware: MiddlewareHandler = async (ctx, next) => { } if (ctx.req.query('filter_description')) { const descriptionRegex = makeRegex(ctx.req.query('filter_description')!); - isFilter = isFilter && (descriptionRegex instanceof RE2JS ? descriptionRegex.matcher(description).find() : !!descriptionRegex.test(description)); + isFilter &&= descriptionRegex instanceof RE2JS ? descriptionRegex.matcher(description).find() : !!descriptionRegex.test(description); } if (ctx.req.query('filter_author')) { const authorRegex = makeRegex(ctx.req.query('filter_author')!); - isFilter = isFilter && (authorRegex instanceof RE2JS ? authorRegex.matcher(author).find() : !!authorRegex.test(author)); + isFilter &&= authorRegex instanceof RE2JS ? authorRegex.matcher(author).find() : !!authorRegex.test(author); } if (ctx.req.query('filter_category')) { const categoryRegex = makeRegex(ctx.req.query('filter_category')!); - isFilter = isFilter && category.some((c) => (categoryRegex instanceof RE2JS ? categoryRegex.matcher(c).find() : c.match(categoryRegex))); + isFilter &&= category.some((c) => (categoryRegex instanceof RE2JS ? categoryRegex.matcher(c).find() : c.match(categoryRegex))); } return isFilter; @@ -256,15 +256,15 @@ const middleware: MiddlewareHandler = async (ctx, next) => { } if (ctx.req.query('filterout') || ctx.req.query('filterout_description')) { const descriptionRegex = makeRegex(ctx.req.query('filterout_description') || ctx.req.query('filterout')!); - isFilter = isFilter && (descriptionRegex instanceof RE2JS ? !descriptionRegex.matcher(description).find() : !descriptionRegex.test(description)); + isFilter &&= descriptionRegex instanceof RE2JS ? !descriptionRegex.matcher(description).find() : !descriptionRegex.test(description); } if (ctx.req.query('filterout_author')) { const authorRegex = makeRegex(ctx.req.query('filterout_author')!); - isFilter = isFilter && (authorRegex instanceof RE2JS ? !authorRegex.matcher(author).find() : !authorRegex.test(author)); + isFilter &&= authorRegex instanceof RE2JS ? !authorRegex.matcher(author).find() : !authorRegex.test(author); } if (ctx.req.query('filterout_category')) { const categoryRegex = makeRegex(ctx.req.query('filterout_category')!); - isFilter = isFilter && !category.some((c) => (categoryRegex instanceof RE2JS ? categoryRegex.matcher(c).find() : c.match(categoryRegex))); + isFilter &&= category.every((c) => !(categoryRegex instanceof RE2JS ? categoryRegex.matcher(c).find() : c.match(categoryRegex))); } return isFilter; @@ -296,9 +296,8 @@ const middleware: MiddlewareHandler = async (ctx, next) => { const encodedlink = encodeURIComponent(item.link); item.link = `https://t.me/iv?url=${encodedlink}&rhash=${ctx.req.query('tgiv')}`; return item; - } else { - return item; } + return item; }); } @@ -407,11 +406,12 @@ const middleware: MiddlewareHandler = async (ctx, next) => { if (num.test(ctx.req.query('brief')!)) { const brief: number = Number.parseInt(ctx.req.query('brief')!); for (const item of data.item) { - let text; - if (item.description) { - text = sanitizeHtml(item.description, { allowedTags: [], allowedAttributes: {} }); - item.description = text.length > brief ? `

${text.slice(0, brief)}…

` : `

${text}

`; + if (!item.description) { + continue; } + + const text = sanitizeHtml(item.description, { allowedTags: [], allowedAttributes: {} }); + item.description = text.length > brief ? `

${text.slice(0, brief)}…

` : `

${text}

`; } } else { throw new Error('Invalid parameter brief. Please check the doc https://docs.rsshub.app/guide/parameters#shu-chu-jian-xun'); diff --git a/lib/middleware/sentry.test.ts b/lib/middleware/sentry.test.ts index 50c88bac0050..f9a5fd55b5ca 100644 --- a/lib/middleware/sentry.test.ts +++ b/lib/middleware/sentry.test.ts @@ -47,6 +47,25 @@ describe('sentry middleware', () => { return { middleware, sentry, logger, scope, getRouteNameFromPath }; }; + it('does not load sentry when dsn is not configured', async () => { + const sentryFactory = vi.fn(() => ({ init: vi.fn() })); + vi.doMock('@sentry/node', sentryFactory); + vi.doMock('@/config', () => ({ + config: { + sentry: { + dsn: '', + }, + errorTrackingRouteTimeout: 50, + nodeName: 'node-a', + }, + })); + + const { default: middleware } = await import('@/middleware/sentry'); + await middleware({ req: { path: '/test/slow' } } as any, async () => {}); + + expect(sentryFactory).not.toHaveBeenCalled(); + }); + it('initializes sentry and captures slow routes', async () => { const { middleware, sentry, logger, scope, getRouteNameFromPath } = await loadMiddleware(); diff --git a/lib/middleware/sentry.ts b/lib/middleware/sentry.ts index efc6f1679e7b..de34a9217359 100644 --- a/lib/middleware/sentry.ts +++ b/lib/middleware/sentry.ts @@ -1,11 +1,14 @@ -import * as Sentry from '@sentry/node'; +import type * as SentryType from '@sentry/node'; import type { MiddlewareHandler } from 'hono'; import { config } from '@/config'; import { getRouteNameFromPath } from '@/utils/helpers'; import logger from '@/utils/logger'; +let Sentry: typeof SentryType | undefined; + if (config.sentry.dsn) { + Sentry = await import('@sentry/node'); Sentry.init({ dsn: config.sentry.dsn, }); @@ -17,10 +20,10 @@ if (config.sentry.dsn) { const middleware: MiddlewareHandler = async (ctx, next) => { const time = Date.now(); await next(); - if (config.sentry.dsn && Date.now() - time >= config.errorTrackingRouteTimeout) { + if (Sentry && Date.now() - time >= config.errorTrackingRouteTimeout) { Sentry.withScope((scope) => { scope.setTag('name', getRouteNameFromPath(ctx.req.path)); - Sentry.captureException(new Error('Route Timeout')); + Sentry!.captureException(new Error('Route Timeout')); }); } }; diff --git a/lib/middleware/template.tsx b/lib/middleware/template.tsx index d9f5ed98655c..ecc947ee5375 100644 --- a/lib/middleware/template.tsx +++ b/lib/middleware/template.tsx @@ -28,7 +28,7 @@ const middleware: MiddlewareHandler = async (ctx, next) => { return ctx.json(ctx.get('json') || { message: 'plugin does not set debug json' }); } - if (/(\d+)\.debug\.html$/.test(outputType)) { + if (/\d+\.debug\.html$/.test(outputType)) { const index = Number.parseInt(outputType.match(/(\d+)\.debug\.html$/)?.[1] || '0'); return ctx.html(data?.item?.[index]?.description || `data.item[${index}].description not found`); } @@ -36,8 +36,8 @@ const middleware: MiddlewareHandler = async (ctx, next) => { if (data) { data.title = collapseWhitespace(data.title) || ''; - data.description && (data.description = collapseWhitespace(data.description) || ''); - data.author && (data.author = collapseWhitespace(data.author) || ''); + data.description &&= collapseWhitespace(data.description) || ''; + data.author &&= collapseWhitespace(data.author) || ''; if (data.item) { for (const item of data.item) { @@ -58,7 +58,8 @@ const middleware: MiddlewareHandler = async (ctx, next) => { // https://stackoverflow.com/questions/1497885/remove-control-characters-from-php-string/1497928#1497928 // remove unicode control characters // see #14940 #14943 #15262 - item.description = item.description.replaceAll(/[\u0000-\u0009\u000B\u000C\u000E-\u001F\u007F\u200B\uFFFF]/g, ''); + // oxlint-disable-next-line no-control-regex + item.description = item.description.replaceAll(/[\u{0000}-\u{0009}\v\f\u{000E}-\u{001F}\u{007F}\u{200B}\u{FFFF}]/gu, ''); } if (typeof item.author === 'string') { @@ -80,12 +81,12 @@ const middleware: MiddlewareHandler = async (ctx, next) => { if (outputType !== 'rss') { try { - item.pubDate && (item.pubDate = convertDateToISO8601(item.pubDate) || ''); + item.pubDate &&= convertDateToISO8601(item.pubDate) || ''; } catch { item.pubDate = ''; } try { - item.updated && (item.updated = convertDateToISO8601(item.updated) || ''); + item.updated &&= convertDateToISO8601(item.updated) || ''; } catch { item.updated = ''; } @@ -109,22 +110,22 @@ const middleware: MiddlewareHandler = async (ctx, next) => { if (ctx.get('redirect')) { return ctx.redirect(ctx.get('redirect'), 301); - } else if (ctx.get('no-content')) { + } + if (ctx.get('no-content')) { return ctx.body(null); - } else { - // retain .ums for backward compatibility - switch (outputType) { - case 'ums': - case 'rss3': - return ctx.json(rss3(result)); - case 'json': - ctx.header('Content-Type', 'application/feed+json; charset=UTF-8'); - return ctx.body(json(result)); - case 'atom': - return ctx.render(); - default: - return ctx.render(); - } + } + // retain .ums for backward compatibility + switch (outputType) { + case 'ums': + case 'rss3': + return ctx.json(rss3(result)); + case 'json': + ctx.header('Content-Type', 'application/feed+json; charset=UTF-8'); + return ctx.body(json(result)); + case 'atom': + return ctx.render(); + default: + return ctx.render(); } }; diff --git a/lib/pkg.ts b/lib/pkg.ts index 37762522c935..6ce9cf5f42b0 100644 --- a/lib/pkg.ts +++ b/lib/pkg.ts @@ -42,7 +42,7 @@ export async function registerRoute(namespace: string, route: Route, namespaceCo const { namespaces } = await import('./registry'); - if (!namespaces[namespace]) { + if (!Object.hasOwn(namespaces, namespace)) { namespaces[namespace] = { ...namespaceConfig, name: namespaceConfig?.name || namespace, @@ -55,13 +55,14 @@ export async function registerRoute(namespace: string, route: Route, namespaceCo const subApp = app.basePath(`/${namespace}`); const wrappedHandler: Handler = async (ctx) => { - if (!ctx.get('data')) { - const response = await route.handler(ctx); - if (response instanceof Response) { - return response; - } - ctx.set('data', response); + if (ctx.get('data')) { + return; } + const response = await route.handler(ctx); + if (response instanceof Response) { + return response; + } + ctx.set('data', response); }; for (const path of paths) { diff --git a/lib/registry.test.ts b/lib/registry.test.ts index dfbdde572c8d..27d691911a15 100644 --- a/lib/registry.test.ts +++ b/lib/registry.test.ts @@ -2,6 +2,8 @@ import { describe, expect, it, vi } from 'vitest'; import app from '@/app'; import { config } from '@/config'; +import registryApp, { collectNamespaceRoots, namespaces, resolveModuleNamespace, sortRoutes } from '@/registry'; +import type { Route } from '@/types'; describe('registry', () => { // root @@ -68,3 +70,70 @@ describe('registry', () => { expect(namespaces['2048']).toBeDefined(); }); }); + +describe('namespace-resolution', () => { + const keys = [ + '/github/namespace.ts', + '/github/issue.ts', + '/example/sub/namespace.ts', + '/example/sub/news.ts', + '/example/flat/typical.ts', + '/example/nested/namespace.ts', + '/example/nested/zzb.ts', + '/example/nested/deep/namespace.ts', + '/example/nested/deep/zwfw.ts', + ]; + const roots = collectNamespaceRoots(keys); + + it('collects directories containing namespace.ts', () => { + expect(roots).toEqual(new Set(['github', 'example/sub', 'example/nested', 'example/nested/deep'])); + }); + + it('resolves a flat module to its top directory', () => { + expect(resolveModuleNamespace('/github/issue.ts', roots)).toEqual({ namespace: 'github', location: 'issue.ts' }); + }); + + it('resolves a nested module to its namespace root', () => { + expect(resolveModuleNamespace('/example/sub/news.ts', roots)).toEqual({ namespace: 'example/sub', location: 'news.ts' }); + }); + + it('prefers the deepest matching root', () => { + expect(resolveModuleNamespace('/example/nested/deep/zwfw.ts', roots)).toEqual({ namespace: 'example/nested/deep', location: 'zwfw.ts' }); + expect(resolveModuleNamespace('/example/nested/zzb.ts', roots)).toEqual({ namespace: 'example/nested', location: 'zzb.ts' }); + }); + + it('falls back to the first segment when no root matches', () => { + expect(resolveModuleNamespace('/example/flat/typical.ts', roots)).toEqual({ namespace: 'example', location: 'flat/typical.ts' }); + }); + + it('resolves namespace.ts itself to its own root', () => { + expect(resolveModuleNamespace('/example/sub/namespace.ts', roots)).toEqual({ namespace: 'example/sub', location: 'namespace.ts' }); + }); +}); + +describe('nested namespace mounting', () => { + it('registers deeper namespaces before shallower ones', () => { + const keys = Object.keys(namespaces).filter((key) => Object.keys(namespaces[key].routes ?? {}).length > 0); + const byDepth = keys.toSorted((a, b) => b.split('/').length - a.split('/').length); + const deep = byDepth[0]; + const shallow = byDepth.at(-1) as string; + expect(deep.split('/').length).toBeGreaterThan(shallow.split('/').length); + + const paths = registryApp.routes.map((r) => r.path); + const deepIndex = paths.indexOf(`/${deep}${Object.keys(namespaces[deep].routes)[0]}`); + const shallowIndex = paths.indexOf(`/${shallow}${Object.keys(namespaces[shallow].routes)[0]}`); + expect(deepIndex).toBeGreaterThanOrEqual(0); + expect(shallowIndex).toBeGreaterThanOrEqual(0); + expect(deepIndex).toBeLessThan(shallowIndex); + }); + + it('sorts regex-constrained params before plain params', () => { + const stub = {} as Route & { location: string }; + const sorted = sortRoutes({ + '/:category?': stub, + '/:id{[0-9]+}': stub, + '/static': stub, + }); + expect(sorted.map(([path]) => path)).toEqual(['/static', '/:id{[0-9]+}', '/:category?']); + }); +}); diff --git a/lib/registry.ts b/lib/registry.ts index 414dfb5d15db..ec12e10a400b 100644 --- a/lib/registry.ts +++ b/lib/registry.ts @@ -17,6 +17,38 @@ import logger from '@/utils/logger'; const __dirname = import.meta.dirname; +const SEPARATOR = /[/\\]/; + +/** + * A directory under lib/routes that contains a `namespace.ts` is a "namespace root". Roots are keyed by their path + * relative to lib/routes, joined with `/` (e.g. `gov/cn`). + */ +export function collectNamespaceRoots(moduleKeys: string[]): Set { + const roots = new Set(); + for (const key of moduleKeys) { + const segments = key.split(SEPARATOR).filter(Boolean); + if (segments.at(-1) === 'namespace.ts') { + roots.add(segments.slice(0, -1).join('/')); + } + } + return roots; +} + +/** + * A module belongs to its longest matching namespace root; modules with no matching root fall back to their first + * path segment (flat namespaces). `location` is the module path relative to the resolved namespace root. + */ +export function resolveModuleNamespace(moduleKey: string, roots: Set): { namespace: string; location: string } { + const segments = moduleKey.split(SEPARATOR).filter(Boolean); + for (let i = segments.length - 1; i >= 1; i--) { + const candidate = segments.slice(0, i).join('/'); + if (roots.has(candidate)) { + return { namespace: candidate, location: segments.slice(i).join('/') }; + } + } + return { namespace: segments[0], location: segments.slice(1).join('/') }; +} + function isSafeRoutes(routes: RoutesType): boolean { return Object.values(routes).every((route: Route) => !route.features?.nsfw); } @@ -84,6 +116,7 @@ if (config.feature.disable_nsfw) { } if (Object.keys(modules).length) { + const namespaceRoots = collectNamespaceRoots(Object.keys(modules)); for (const module in modules) { const content = modules[module] as | { @@ -95,7 +128,7 @@ if (Object.keys(modules).length) { | { apiRoute: APIRoute; }; - const namespace = module.split(/[/\\]/)[1]; + const { namespace, location } = resolveModuleNamespace(module, namespaceRoots); if ('namespace' in content) { namespaces[namespace] = Object.assign( { @@ -106,7 +139,7 @@ if (Object.keys(modules).length) { content.namespace ); } else if ('route' in content) { - if (!namespaces[namespace]) { + if (!Object.hasOwn(namespaces, namespace)) { namespaces[namespace] = { name: namespace, routes: {}, @@ -117,17 +150,17 @@ if (Object.keys(modules).length) { for (const path of content.route.path) { namespaces[namespace].routes[path] = { ...content.route, - location: module.split(/[/\\]/).slice(2).join('/'), + location, }; } } else { namespaces[namespace].routes[content.route.path] = { ...content.route, - location: module.split(/[/\\]/).slice(2).join('/'), + location, }; } } else if ('apiRoute' in content) { - if (!namespaces[namespace]) { + if (!Object.hasOwn(namespaces, namespace)) { namespaces[namespace] = { name: namespace, routes: {}, @@ -138,13 +171,13 @@ if (Object.keys(modules).length) { for (const path of content.apiRoute.path) { namespaces[namespace].apiRoutes[path] = { ...content.apiRoute, - location: module.split(/[/\\]/).slice(2).join('/'), + location, }; } } else { namespaces[namespace].apiRoutes[content.apiRoute.path] = { ...content.apiRoute, - location: module.split(/[/\\]/).slice(2).join('/'), + location, }; } } @@ -154,7 +187,7 @@ if (Object.keys(modules).length) { export { namespaces }; const app = new Hono(); -const sortRoutes = ( +export const sortRoutes = ( routes: Record< string, Route & { @@ -178,12 +211,20 @@ const sortRoutes = ( if (segmentA.startsWith(':') !== segmentB.startsWith(':')) { return segmentA.startsWith(':') ? 1 : -1; } + + // Regex-constrained parameters have priority over plain parameters + if (segmentA.startsWith(':') && segmentA.includes('{') !== segmentB.includes('{')) { + return segmentA.includes('{') ? -1 : 1; + } } return 0; }); -for (const namespace in namespaces) { +// Deeper namespaces register first so a parent's param routes cannot shadow them +const namespacesByDepth = Object.keys(namespaces).toSorted((a, b) => b.split('/').length - a.split('/').length); + +for (const namespace of namespacesByDepth) { const subApp = app.basePath(`/${namespace}`); const namespaceData = namespaces[namespace]; @@ -217,7 +258,7 @@ for (const namespace in namespaces) { } } -for (const namespace in namespaces) { +for (const namespace of namespacesByDepth) { const subApp = app.basePath(`/api/${namespace}`); const namespaceData = namespaces[namespace]; @@ -237,19 +278,20 @@ for (const namespace in namespaces) { for (const [path, routeData] of sortedRoutes) { const wrappedHandler: Handler = async (ctx) => { - if (!ctx.get('apiData')) { - if (typeof routeData.handler !== 'function') { - if (process.env.NODE_ENV === 'test') { - const { apiRoute } = await import(`./routes/${namespace}/${routeData.location}`); - routeData.handler = apiRoute.handler; - } else if (routeData.module) { - const { apiRoute } = await routeData.module(); - routeData.handler = apiRoute.handler; - } + if (ctx.get('apiData')) { + return; + } + if (typeof routeData.handler !== 'function') { + if (process.env.NODE_ENV === 'test') { + const { apiRoute } = await import(`./routes/${namespace}/${routeData.location}`); + routeData.handler = apiRoute.handler; + } else if (routeData.module) { + const { apiRoute } = await routeData.module(); + routeData.handler = apiRoute.handler; } - const data = await routeData.handler(ctx); - ctx.set('apiData', data); } + const data = await routeData.handler(ctx); + ctx.set('apiData', data); }; subApp.get(path, wrappedHandler); } diff --git a/lib/router.js b/lib/router.js index 921eaa57a414..4293ac9e1413 100644 --- a/lib/router.js +++ b/lib/router.js @@ -413,10 +413,6 @@ router.get('/gov/mohurd/policy', lazyloadRouteHandler('./routes/gov/mohurd/polic // 国家新闻出版广电总局 router.get('/gov/sapprft/approval/:channel/:detail?', lazyloadRouteHandler('./routes/gov/sapprft/7026')); -// 国家新闻出版署 -router.get('/gov/nppa/:channel', lazyloadRouteHandler('./routes/gov/nppa/channels')); -router.get('/gov/nppa/:channel/:content', lazyloadRouteHandler('./routes/gov/nppa/contents')); - // 北京卫生健康委员会 router.get('/gov/beijing/mhc/:caty', lazyloadRouteHandler('./routes/gov/beijing/mhc')); diff --git a/lib/routes-deprecated/gov/nppa/channels.js b/lib/routes-deprecated/gov/nppa/channels.js deleted file mode 100644 index bf3e5621c9dc..000000000000 --- a/lib/routes-deprecated/gov/nppa/channels.js +++ /dev/null @@ -1,58 +0,0 @@ -const got = require('@/utils/got'); -const cheerio = require('cheerio'); -const { parseRelativeDate } = require('@/utils/parse-date'); -const timezone = require('@/utils/timezone'); - -module.exports = async (ctx) => { - const { channel } = ctx.params; - const host = `http://www.nppa.gov.cn`; - const link = host + `/nppa/channels/${channel}.shtml`; - const fullpage = await got.get(link + '?' + Date.now()); // 避免CDN缓存 - if (~fullpage.data.indexOf('-404')) { - ctx.throw(404); - } - const $ = cheerio.load(fullpage.data); - const target = $('ul.m2c2ul li, ul.m2nrul li'); - ctx.state.data = { - title: '国家新闻出版署 - ' + $('.m2nRt, .m2Top_em').text().trim(), - link, - item: await Promise.all( - target - .map(async (index, item) => { - item = $(item); - const href = item.find('a').attr('href'); - let contenlUrl, - description = ''; - if (/^https?:\/\//.test(href)) { - contenlUrl = href; - } else { - contenlUrl = host + href; - description = await ctx.cache.tryGet(contenlUrl, async () => { - const fullText = await got.get(contenlUrl); - const $$ = cheerio.load(fullText.data); - if (~$$('.m2pos').text().indexOf('游戏审批结果')) { - let fullTextData = ''; - $$('.m3pageCon table.trStyle tbody tr') - .slice(1) - .each((index, item) => { - item = $$(item).find('td'); - fullTextData += $$(item[1]).text().trim() + ' | '; - }); - return fullTextData.slice(0, -3); - } else { - $$('.m3pageCon style, .m3pageCon script').remove(); - return $$('.m3pageCon').text().trim().slice(0, 1024); - } - }); - } - return { - title: item.find('a').text().trim(), - description, - pubDate: timezone(parseRelativeDate(item.find('span').text()), 8), - link: contenlUrl, - }; - }) - .get() - ), - }; -}; diff --git a/lib/routes-deprecated/gov/nppa/contents.js b/lib/routes-deprecated/gov/nppa/contents.js deleted file mode 100644 index 4ce146424901..000000000000 --- a/lib/routes-deprecated/gov/nppa/contents.js +++ /dev/null @@ -1,38 +0,0 @@ -const got = require('@/utils/got'); -const cheerio = require('cheerio'); -const { parseRelativeDate } = require('@/utils/parse-date'); -const timezone = require('@/utils/timezone'); - -module.exports = async (ctx) => { - const { channel, content } = ctx.params; - const host = `http://www.nppa.gov.cn`; - const link = host + `/nppa/contents/${channel}/${content}.shtml`; - const fullpage = await got.get(link + '?' + Date.now()); // 避免CDN缓存 - if (~fullpage.data.indexOf('-404')) { - ctx.throw(404); - } - const $ = cheerio.load(fullpage.data); - const list = $('.m3pageCon table.trStyle tbody tr'); - - ctx.state.data = { - title: '国家新闻出版署 - ' + $('.m3page_t').text().trim(), - link, - item: await Promise.all( - list - .slice(1) - .map((index, item) => { - item = $(item).find('td'); - return { - title: $(item[1]).text().trim(), - category: $(item[2]).text().trim(), - description: $(item[5]).text().trim(), - author: $(item[3]).text().trim() + ' | ' + $(item[4]).text().trim(), - pubDate: timezone(parseRelativeDate($(item[7]).text().trim()), 8), - guid: $(item[6]).text().trim(), - link, - }; - }) - .get() - ), - }; -}; diff --git a/lib/routes/005/index.tsx b/lib/routes/005/index.tsx index 43c2be4b5b92..734edf37ca00 100644 --- a/lib/routes/005/index.tsx +++ b/lib/routes/005/index.tsx @@ -9,7 +9,7 @@ import timezone from '@/utils/timezone'; export const handler = async (ctx) => { const { category = 'zx' } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const rootUrl = 'https://005.tv'; const currentUrl = new URL(category ? `${category}/` : '', rootUrl).href; @@ -67,7 +67,7 @@ export const handler = async (ctx) => { item.title = title; item.description = description; - item.pubDate = timezone(parseDate($$('.time').text()), +8); + item.pubDate = timezone(parseDate($$('.time').text()), 8); item.category = $$('meta[name="keywords"]').prop('content').split(/,/); item.content = { html: description, @@ -85,7 +85,7 @@ export const handler = async (ctx) => { return { title, - description: title.split(/_/)[0], + description: title.split(/_/, 1)[0], link: currentUrl, item: items, allowEmpty: true, diff --git a/lib/routes/0818tuan/index.ts b/lib/routes/0818tuan/index.ts index a6df25136051..39fcc7f56fa6 100644 --- a/lib/routes/0818tuan/index.ts +++ b/lib/routes/0818tuan/index.ts @@ -55,7 +55,7 @@ async function handler(ctx) { $('.pageLink, .alert, p[style="margin:15px;"]').remove(); item.description = $('.post-content').html(); - item.pubDate = timezone(parseDate($('.panel-body > .text-center').text().replace('时间:', ''), 'YYYY-MM-DD HH:mm:ss'), +8); + item.pubDate = timezone(parseDate($('.panel-body > .text-center').text().replace('时间:', ''), 'YYYY-MM-DD HH:mm:ss'), 8); return item; }) diff --git a/lib/routes/0xxx/index.ts b/lib/routes/0xxx/index.ts index 78ff90a68380..bfe02481d7d9 100644 --- a/lib/routes/0xxx/index.ts +++ b/lib/routes/0xxx/index.ts @@ -13,7 +13,7 @@ import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { filter } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '100', 10); + const limit = Number(ctx.req.query('limit') ?? '100'); const baseUrl = 'https://0xxx.ws'; const targetUrl: string = new URL(filter ? `?${filter}` : '', baseUrl).href; diff --git a/lib/routes/10000link/info.ts b/lib/routes/10000link/info.ts index 3b7adf2caf75..cbf4e69b72b0 100644 --- a/lib/routes/10000link/info.ts +++ b/lib/routes/10000link/info.ts @@ -13,7 +13,7 @@ import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category = 'newslists', id } = 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://info.10000link.com'; const targetUrl: string = new URL(`${category}.aspx${id ? `?chid=${id}` : ''}`, baseUrl).href; diff --git a/lib/routes/10jqka/realtimenews.ts b/lib/routes/10jqka/realtimenews.ts index 684217bfd7fb..6b8742133abd 100644 --- a/lib/routes/10jqka/realtimenews.ts +++ b/lib/routes/10jqka/realtimenews.ts @@ -7,7 +7,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx) => { const { tag } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const rootUrl = 'https://news.10jqka.com.cn'; const apiUrl = new URL('tapp/news/push/stock', rootUrl).href; diff --git a/lib/routes/121/weather-live.tsx b/lib/routes/121/weather-live.tsx index 63e586781810..bc7b70de8510 100644 --- a/lib/routes/121/weather-live.tsx +++ b/lib/routes/121/weather-live.tsx @@ -25,7 +25,7 @@ const renderDescription = (description, images) => ); export const handler = async (ctx: Context): Promise => { - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '100', 10); + const limit = Number(ctx.req.query('limit') ?? '100'); const baseUrl = 'https://tf.121.com.cn'; const imgBaseUrl = 'https://wx.121.com.cn'; diff --git a/lib/routes/12371/zxfb.ts b/lib/routes/12371/zxfb.ts index e5049220597f..ff1ac554abe5 100644 --- a/lib/routes/12371/zxfb.ts +++ b/lib/routes/12371/zxfb.ts @@ -8,7 +8,7 @@ import timezone from '@/utils/timezone'; const handler = async (ctx) => { const { category = 'zxfb' } = 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 = 'https://www.12371.cn/'; const currentUrl = `${rootUrl}${category}/`; @@ -16,7 +16,7 @@ const handler = async (ctx) => { const $ = cheerio.load(response.data); - const pattern = /item=(\[{.*?}]);/; + const pattern = /item=(\[\{.*?\}\]);/; const newsList = JSON.parse($('script[language="javascript"]').text().match(pattern)?.[1].replaceAll("'", '"') || '[]'); const topNewsList = newsList.slice(0, limit).map((item) => ({ diff --git a/lib/routes/141jav/index.tsx b/lib/routes/141jav/index.tsx index 350aa2b25cce..03818ae3fecb 100644 --- a/lib/routes/141jav/index.tsx +++ b/lib/routes/141jav/index.tsx @@ -7,7 +7,7 @@ import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; export const route: Route = { - path: '/:type/:keyword{.*}?', + path: '/:type/:keyword{.+}?', categories: ['multimedia'], name: '通用', maintainers: ['cgkings', 'nczitzk'], @@ -104,7 +104,7 @@ async function handler(ctx) { }); return { - title: `141JAV - ${$('title').text().split('-')[0].trim()}`, + title: `141JAV - ${$('title').text().split('-', 1)[0].trim()}`, link: currentUrl, item: items, }; diff --git a/lib/routes/141ppv/index.tsx b/lib/routes/141ppv/index.tsx index 4d27b39b20c3..4cd480a5396c 100644 --- a/lib/routes/141ppv/index.tsx +++ b/lib/routes/141ppv/index.tsx @@ -7,7 +7,7 @@ import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; export const route: Route = { - path: '/:type/:keyword{.*}?', + path: '/:type/:keyword{.+}?', categories: ['multimedia'], name: '通用', maintainers: ['cgkings', 'nczitzk'], @@ -163,7 +163,7 @@ async function handler(ctx) { }); return { - title: `141PPV - ${$('title').text().split('-')[0].trim()}`, + title: `141PPV - ${$('title').text().split('-', 1)[0].trim()}`, link: currentUrl, item: items, }; diff --git a/lib/routes/163/dy.ts b/lib/routes/163/dy.ts index 8b77df942391..2720d00520fb 100644 --- a/lib/routes/163/dy.ts +++ b/lib/routes/163/dy.ts @@ -1,5 +1,4 @@ import type { Route } from '@/types'; -import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; @@ -40,7 +39,7 @@ async function handler(ctx) { imgsrc: e.imgsrc, })); - const items = await Promise.all(list.map((e) => parseDyArticle(e, cache.tryGet))); + const items = await Promise.all(list.map((e) => parseDyArticle(e))); return { title: `网易号 - ${list[0].author}`, diff --git a/lib/routes/163/dy2.ts b/lib/routes/163/dy2.ts index 6c65b2878279..06ff453d9146 100644 --- a/lib/routes/163/dy2.ts +++ b/lib/routes/163/dy2.ts @@ -1,7 +1,6 @@ import { load } from 'cheerio'; import type { Route } from '@/types'; -import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; @@ -49,7 +48,7 @@ async function handler(ctx) { }; }); - const items = await Promise.all(list.map((item) => parseDyArticle(item, cache.tryGet))); + const items = await Promise.all(list.map((item) => parseDyArticle(item))); return { title: `${$('head title').text()} - 网易号`, diff --git a/lib/routes/163/exclusive.ts b/lib/routes/163/exclusive.ts index ef265de1b503..881efdb75046 100644 --- a/lib/routes/163/exclusive.ts +++ b/lib/routes/163/exclusive.ts @@ -134,7 +134,7 @@ async function handler(ctx) { title: item.title, author: item.source, link: item.skipURL || item.url || `${rootUrl}/dy/article/${item.docid}.html`, - pubDate: timezone(parseDate(item.ptime), +8), + pubDate: timezone(parseDate(item.ptime), 8), videoId: item.skipType === 'video' ? item.stitle : '', })); diff --git a/lib/routes/163/music/artist.ts b/lib/routes/163/music/artist.ts index a555357f4390..4f4b6edeccb5 100644 --- a/lib/routes/163/music/artist.ts +++ b/lib/routes/163/music/artist.ts @@ -38,7 +38,10 @@ async function handler(ctx) { description: `网易云音乐歌手专辑 - ${data.artist.name}`, image: data.artist.img1v1Url || data.artist.picUrl, item: data.hotAlbums.map((item) => { - const singer = item.artists.length === 1 ? item.artists[0].name : item.artists.reduce((prev, cur) => (prev.name || prev) + '/' + cur.name); + let singer = item.artists[0].name; + for (const artist of item.artists.slice(1)) { + singer += '/' + artist.name; + } return { title: `${item.name} - ${singer}`, description: renderPlaylistDescription({ diff --git a/lib/routes/163/music/playlist.ts b/lib/routes/163/music/playlist.ts index 8585b120194d..173b3b308b93 100644 --- a/lib/routes/163/music/playlist.ts +++ b/lib/routes/163/music/playlist.ts @@ -55,7 +55,10 @@ async function handler(ctx) { description: `网易云音乐歌单 - ${data.name}`, item: data.trackIds.slice(0, 201).map((item) => { const thisSong = songs.find((element) => element.id === item.id); - const singer = thisSong.artists.length === 1 ? thisSong.artists[0].name : thisSong.artists.reduce((prev, cur) => (prev.name || prev) + '/' + cur.name); + let singer = thisSong.artists[0].name; + for (const artist of thisSong.artists.slice(1)) { + singer += '/' + artist.name; + } return { title: `${thisSong.name} - ${singer}`, description: renderPlaylistDescription({ diff --git a/lib/routes/163/news/rank.ts b/lib/routes/163/news/rank.ts index d7643a8d3aa0..8cd03a5bf55e 100644 --- a/lib/routes/163/news/rank.ts +++ b/lib/routes/163/news/rank.ts @@ -122,7 +122,8 @@ async function handler(ctx) { const cfg = config[category]; if (!cfg) { throw new InvalidParameterError('Bad category. See docs'); - } else if ((category !== 'whole' && type === 'click' && time === 'month') || (category === 'whole' && type === 'click' && time === 'hour') || (type === 'follow' && time === 'hour')) { + } + if ((category !== 'whole' && type === 'click' && time === 'month') || (category === 'whole' && type === 'click' && time === 'hour') || (type === 'follow' && time === 'hour')) { throw new InvalidParameterError('Bad timeRange range. See docs'); } @@ -152,8 +153,8 @@ async function handler(ctx) { cache.tryGet(item.link, async () => { try { let link; - if (category === 'auto' || category === 'house' || category === 'travel') { - const category = item.link.split('.163.com')[0].split('//').pop().split('.').pop(); + if (['auto', 'house', 'travel'].includes(category)) { + const category = item.link.split('.163.com', 1)[0].split('//').pop().split('.').pop(); link = `https://3g.163.com/${category}/article/${item.link.split('/').pop()}`; } else { const pathname = new URL(item.link).pathname; diff --git a/lib/routes/163/news/special.ts b/lib/routes/163/news/special.ts index ea7d1948c278..86f93caba8df 100644 --- a/lib/routes/163/news/special.ts +++ b/lib/routes/163/news/special.ts @@ -102,7 +102,7 @@ async function handler(ctx) { const url = `https://3g.163.com/touch/reconstruct/article/list/${type}/0-20.html`; const response = await got(url); const data = response.data; - const matches = data.replaceAll(/\s/g, '').match(/artiList\((.*?)]}\)/); + const matches = data.replaceAll(/\s/g, '').match(/artiList\((.*?)\]\}\)/); const articlelist0 = matches[1].replace(/".*?wangning/, '"articles') + ']}'; const articlelist = JSON.parse(articlelist0); const articles = articlelist.articles; @@ -112,7 +112,7 @@ async function handler(ctx) { let url = article.url; if (url === null || article.skipType === 'video') { const skipurl = article.skipURL; - const vid = skipurl.match(/vid=(.*?)$/); + const vid = skipurl.match(/vid=(.*)$/); if (vid !== null) { url = `https://3g.163.com/exclusive/video/${vid[1]}.html`; } diff --git a/lib/routes/163/open/vip.tsx b/lib/routes/163/open/vip.tsx index 4ee699dbc0dd..82784e98fb50 100644 --- a/lib/routes/163/open/vip.tsx +++ b/lib/routes/163/open/vip.tsx @@ -65,7 +65,7 @@ async function handler() { const initialState = JSON.parse( $('script') .text() - .match(/window\.__INITIAL_STATE__=(.*);\(function\(\){var/)[1] + .match(/window\.__INITIAL_STATE__=(.*);\(function\(\)\{var/)[1] ); const list = Object.values(initialState.courseindex.myModules).flatMap((mod) => @@ -93,7 +93,7 @@ async function handler() { const $ = load(data.courseInfo.description, null, false); $('img').each((_, img) => { - img.attribs.src = img.attribs.src.split('?')[0]; + img.attribs.src = img.attribs.src.split('?', 1)[0]; delete img.attribs.width; }); diff --git a/lib/routes/163/renjian.ts b/lib/routes/163/renjian.ts index c1f92c510a8d..d3d066ac67f1 100644 --- a/lib/routes/163/renjian.ts +++ b/lib/routes/163/renjian.ts @@ -91,7 +91,7 @@ async function handler(ctx) { .text() .match(/renjian_author = '(.*)'/)[1]; item.description = content('#endText').html() ?? content('#content').html(); - item.pubDate = timezone(parseDate(content('.pub_time').text() ?? content('.post_info').text().split('来源:')[0].trim()), 8); + item.pubDate = timezone(parseDate(content('.pub_time').text() ?? content('.post_info').text().split('来源:', 1)[0].trim()), 8); return item; }) diff --git a/lib/routes/163/today.ts b/lib/routes/163/today.ts index d259642cefb1..5ccefa8625cc 100644 --- a/lib/routes/163/today.ts +++ b/lib/routes/163/today.ts @@ -48,7 +48,7 @@ async function handler(ctx) { let items = response.data.data.items.map((item) => ({ title: item.title, author: item.source, - pubDate: timezone(parseDate(item.ptime), +8), + pubDate: timezone(parseDate(item.ptime), 8), description: `

${item.digest}

`, link: item.url || `https://c.m.163.com/news/a/${item.docid}.html`, })); diff --git a/lib/routes/163/utils.tsx b/lib/routes/163/utils.tsx index df91f2032298..2c2f109a8f15 100644 --- a/lib/routes/163/utils.tsx +++ b/lib/routes/163/utils.tsx @@ -2,6 +2,7 @@ import { load } from 'cheerio'; import { raw } from 'hono/html'; import { renderToString } from 'hono/jsx/dom/server'; +import cache from '@/utils/cache'; import got from '@/utils/got'; const renderDescription = (imgsrc, postBody) => @@ -17,8 +18,8 @@ const renderDescription = (imgsrc, postBody) => ); -const parseDyArticle = (item, tryGet) => - tryGet(item.link, async () => { +const parseDyArticle = (item) => + cache.tryGet(item.link, async () => { const response = await got(item.link, { responseType: 'buffer', }); diff --git a/lib/routes/18comic/blogs.ts b/lib/routes/18comic/blogs.ts index 0f127d966982..65399a95c3dd 100644 --- a/lib/routes/18comic/blogs.ts +++ b/lib/routes/18comic/blogs.ts @@ -91,7 +91,7 @@ async function handler(ctx) { return { title: $('title') .text() - .replace(/最新的/, $('.article-nav .active').text()), + .replace(/最新的/, () => $('.article-nav .active').text()), link: currentUrl, item: items, description: $('meta[property="og:description"]').attr('content'), diff --git a/lib/routes/18comic/search.ts b/lib/routes/18comic/search.ts index ffd8324b8070..3b9a81c78b48 100644 --- a/lib/routes/18comic/search.ts +++ b/lib/routes/18comic/search.ts @@ -56,7 +56,7 @@ async function handler(ctx) { let apiUrl = getApiUrl(); order = time === 'a' ? order : `${order}_${time}`; - apiUrl = `${apiUrl}/search?search_query=${encodedKeyword}&o=${order}`; + apiUrl += `/search?search_query=${encodedKeyword}&o=${order}`; const apiResult = await processApiItems(apiUrl); let filteredItemsByCategory = apiResult.content; // Filter items by category if not 'all' diff --git a/lib/routes/199it/index.tsx b/lib/routes/199it/index.tsx index 39d540fd3a32..b1d7ac29be1a 100644 --- a/lib/routes/199it/index.tsx +++ b/lib/routes/199it/index.tsx @@ -12,7 +12,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx: Context): Promise => { const { category = 'newly' } = 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.199it.com'; const targetUrl: string = new URL(category, baseUrl).href; diff --git a/lib/routes/19lou/index.ts b/lib/routes/19lou/index.ts index 579552d48eb4..00c01289a3e3 100644 --- a/lib/routes/19lou/index.ts +++ b/lib/routes/19lou/index.ts @@ -9,7 +9,7 @@ import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; import { isValidHost } from '@/utils/valid-host'; -const setCookie = function (cookieName, cookieValue, seconds, path, domain, secure) { +const setCookie = function (cookieName, cookieValue, seconds, path, domain, secure?) { let expires = null; if (seconds !== -1) { expires = new Date(); @@ -100,7 +100,7 @@ async function handler(ctx) { item.author = content('.uname, .user-name').first().text(); item.description = content('.post-cont').first().html() || content('.thread-cont').html(); - item.pubDate = timezone(parseDate(content('.cont-top-left meta').first().attr('content')), +8); + item.pubDate = timezone(parseDate(content('.cont-top-left meta').first().attr('content')), 8); return item; }) @@ -108,7 +108,7 @@ async function handler(ctx) { ); return { - title: $('title').text().split('-')[0], + title: $('title').text().split('-', 1)[0], link: rootUrl, item: items, }; diff --git a/lib/routes/1lou/index.ts b/lib/routes/1lou/index.ts index 88bfc90e82a5..166c57a10c89 100644 --- a/lib/routes/1lou/index.ts +++ b/lib/routes/1lou/index.ts @@ -10,7 +10,7 @@ const rootUrl = 'https://www.1lou.me'; export const handler = async (ctx) => { const { params } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 50; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 50; const queryString = Object.entries(ctx.req.query()) .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) @@ -34,7 +34,7 @@ export const handler = async (ctx) => { return { title: subjectEl.text(), - pubDate: timezone(parseDate(item.find('span.date').text()), +8), + pubDate: timezone(parseDate(item.find('span.date').text()), 8), link: new URL(subjectEl.prop('href'), rootUrl).href, category: [ item.find('a.text-secondary').text().replaceAll('[]', ''), @@ -63,7 +63,7 @@ export const handler = async (ctx) => { item.title = title; item.description = description; - item.pubDate = timezone(parseDate($$('span.date').text()), +8); + item.pubDate = timezone(parseDate($$('span.date').text()), 8); item.category = $$('a.badge') .toArray() .map((c) => $$(c).text()); @@ -95,7 +95,7 @@ export const handler = async (ctx) => { const image = new URL($('img.logo-2').prop('src'), rootUrl).href; return { - title: `${$('title').text().split(/-/)[0]} - ${author}`, + title: `${$('title').text().split(/-/, 1)[0]} - ${author}`, description: $('meta[name="description"]').prop('content'), link: currentUrl, item: items, diff --git a/lib/routes/1point3acres/category.ts b/lib/routes/1point3acres/category.ts index 70e51ad01668..4066df478198 100644 --- a/lib/routes/1point3acres/category.ts +++ b/lib/routes/1point3acres/category.ts @@ -1,5 +1,4 @@ import type { Route } from '@/types'; -import cache from '@/utils/cache'; import { apiRootUrl, ProcessThreads, rootUrl, types } from './utils'; @@ -53,6 +52,6 @@ async function handler(ctx) { return { title: `一亩三分地 - ${id}${types[type]}`, link: currentUrl, - item: await ProcessThreads(cache.tryGet, apiUrl, order), + item: await ProcessThreads(apiUrl, order), }; } diff --git a/lib/routes/1point3acres/section.ts b/lib/routes/1point3acres/section.ts index e9a304b49439..2eb3713b4cf5 100644 --- a/lib/routes/1point3acres/section.ts +++ b/lib/routes/1point3acres/section.ts @@ -1,5 +1,4 @@ import type { Route } from '@/types'; -import cache from '@/utils/cache'; import { apiRootUrl, ProcessThreads, rootUrl, types } from './utils'; @@ -73,6 +72,6 @@ async function handler(ctx) { return { title: `一亩三分地 - ${Object.hasOwn(sections, id) ? sections[id] : id}${types[type]}`, link: currentUrl, - item: await ProcessThreads(cache.tryGet, apiUrl, order), + item: await ProcessThreads(apiUrl, order), }; } diff --git a/lib/routes/1point3acres/thread.ts b/lib/routes/1point3acres/thread.ts index 624f4550ab92..8bf50056935b 100644 --- a/lib/routes/1point3acres/thread.ts +++ b/lib/routes/1point3acres/thread.ts @@ -1,5 +1,4 @@ import type { Route } from '@/types'; -import cache from '@/utils/cache'; import { apiRootUrl, ProcessThreads, rootUrl, types } from './utils'; @@ -35,6 +34,6 @@ async function handler(ctx) { return { title: `一亩三分地 - ${types[type]}`, link: rootUrl, - item: await ProcessThreads(cache.tryGet, apiUrl, order), + item: await ProcessThreads(apiUrl, order), }; } diff --git a/lib/routes/1point3acres/utils.tsx b/lib/routes/1point3acres/utils.tsx index 746732d2671d..eee2f2f238b8 100644 --- a/lib/routes/1point3acres/utils.tsx +++ b/lib/routes/1point3acres/utils.tsx @@ -3,6 +3,7 @@ import presetHTML5 from '@bbob/preset-html5'; import type { BBobCoreTagNodeTree } from '@bbob/types'; import { renderToString } from 'hono/jsx/dom/server'; +import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; @@ -25,7 +26,7 @@ const swapLinebreak = (tree: BBobCoreTagNodeTree) => return node; }); -const ProcessThreads = async (tryGet, apiUrl, order) => { +const ProcessThreads = async (apiUrl, order) => { const response = await got({ method: 'get', url: apiUrl, @@ -46,7 +47,7 @@ const ProcessThreads = async (tryGet, apiUrl, order) => { category: [item.forum_name, ...(item.tags ? item.tags.map((t) => t.displayname) : [])], }; - return tryGet(result.link, async () => { + return cache.tryGet(result.link, async () => { try { const detailResponse = await got({ method: 'get', diff --git a/lib/routes/1x/index.tsx b/lib/routes/1x/index.tsx index 83e0f52d2ebb..b89d21b67934 100644 --- a/lib/routes/1x/index.tsx +++ b/lib/routes/1x/index.tsx @@ -7,7 +7,7 @@ import got from '@/utils/got'; export const handler = async (ctx) => { const { category = 'latest/awarded' } = 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://1x.com'; const currentUrl = new URL(`gallery/${category}`, rootUrl).href; diff --git a/lib/routes/2048/index.tsx b/lib/routes/2048/index.tsx index 46c1e53563c2..6feaef97415e 100644 --- a/lib/routes/2048/index.tsx +++ b/lib/routes/2048/index.tsx @@ -143,7 +143,7 @@ async function handler(ctx) { }); item.author = content('.fl.black').first().text(); - item.pubDate = timezone(parseDate(content('span.fl.gray').first().attr('title')), +8); + item.pubDate = timezone(parseDate(content('span.fl.gray').first().attr('title')), 8); const readTpc = content('#read_tpc').first(); const copyLink = content('#copytext')?.first()?.text(); @@ -164,7 +164,7 @@ async function handler(ctx) { } } if (!item.enclosure_url) { - const hashMatch = readTpcHtml.match(/哈希校验[^;]*;\s*([a-fA-F0-9]{40})\s*[;;]/); + const hashMatch = readTpcHtml.match(/哈希校验[^;]*;\s*([a-f0-9]{40})\s*[;;]/i); const magnetFromHash = hashMatch ? `magnet:?xt=urn:btih:${hashMatch[1]}` : null; const magnetFromText = magnetText.match(/magnet:\?xt=urn:btih:[^\s"'<>]+/)?.[0]; const magnetLink = magnetFromText ?? readTpcHtml.match(/magnet:\?xt=urn:btih:[^\s"'<>]+/)?.[0] ?? magnetFromHash ?? copyLink; diff --git a/lib/routes/21caijing/channel.ts b/lib/routes/21caijing/channel.ts index 0ec8a3dc6a2d..f0e235293a91 100644 --- a/lib/routes/21caijing/channel.ts +++ b/lib/routes/21caijing/channel.ts @@ -43,7 +43,7 @@ const processMenu = (data: any[]) => { export const handler = async (ctx: Context): Promise => { const { name = '热点' } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); + const limit = Number(ctx.req.query('limit') ?? '30'); const domain = 'm.21jingji.com'; const baseUrl = `https://${domain}`; @@ -101,7 +101,7 @@ export const handler = async (ctx: Context): Promise => { const processedItem: DataItem = { title, - pubDate: pubDate ? timezone(parseRelativeDate(pubDate), +8) : undefined, + pubDate: pubDate ? timezone(parseRelativeDate(pubDate), 8) : undefined, link: linkUrl, category: categories, author: authors, @@ -141,13 +141,13 @@ export const handler = async (ctx: Context): Promise => { const processedItem: DataItem = { title, description, - pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : item.pubDate, + pubDate: pubDateStr ? timezone(parseDate(pubDateStr), 8) : item.pubDate, category: categories, content: { html: description, text: description, }, - updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : item.updated, + updated: upDatedStr ? timezone(parseDate(upDatedStr), 8) : item.updated, language, }; diff --git a/lib/routes/2cycd/index.ts b/lib/routes/2cycd/index.ts index 5a5ad7cf1524..4b52e75997e9 100644 --- a/lib/routes/2cycd/index.ts +++ b/lib/routes/2cycd/index.ts @@ -53,7 +53,7 @@ async function handler(ctx) { const first_post = content('td[id^="postmessage_"]').first(); const dateobj = content('em[id^="authorposton"]').first(); item.description = first_post.html(); - item.pubDate = timezone(parseDate(dateobj.find('span').attr('title'), 'YYYY-M-D HH:mm:ss'), +8); + item.pubDate = timezone(parseDate(dateobj.find('span').attr('title'), 'YYYY-M-D HH:mm:ss'), 8); return item; }) diff --git a/lib/routes/30secondsofcode/category.ts b/lib/routes/30secondsofcode/category.ts index 71baa0a42549..cd19f4b79624 100644 --- a/lib/routes/30secondsofcode/category.ts +++ b/lib/routes/30secondsofcode/category.ts @@ -8,7 +8,7 @@ import { processList } from './utils'; export const route: Route = { path: '/category/:category?/:subCategory?', categories: ['programming'], - example: '/category/css/interactivity', + example: '/30secondsofcode/category/css/interactivity', parameters: { category: { description: 'Main Category. For Complete list visit site "https://www.30secondsofcode.org/collections/p/1/"', diff --git a/lib/routes/30secondsofcode/new-and-popular.ts b/lib/routes/30secondsofcode/new-and-popular.ts index 1ab63ca40d90..44896648ebb0 100644 --- a/lib/routes/30secondsofcode/new-and-popular.ts +++ b/lib/routes/30secondsofcode/new-and-popular.ts @@ -8,7 +8,7 @@ import { processList, rootUrl } from './utils'; export const route: Route = { path: '/latest', categories: ['programming'], - example: '/latest', + example: '/30secondsofcode/latest', features: { requireConfig: false, requirePuppeteer: false, diff --git a/lib/routes/36kr/hot-list.ts b/lib/routes/36kr/hot-list.ts index c28b4f287e81..e7c486c4fa62 100644 --- a/lib/routes/36kr/hot-list.ts +++ b/lib/routes/36kr/hot-list.ts @@ -1,6 +1,5 @@ import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; -import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; @@ -56,7 +55,7 @@ const getProperty = (object, key) => { let result = object; const keys = key.split('.'); for (const k of keys) { - result = result && result[k]; + result &&= result[k]; } return result; }; @@ -64,7 +63,7 @@ const getProperty = (object, key) => { async function handler(ctx) { const category = ctx.req.param('category') ?? '24'; - if (!categories[category]) { + if (!Object.hasOwn(categories, category)) { throw new InvalidParameterError('This category does not exist. Please refer to the documentation for the correct usage.'); } @@ -79,7 +78,7 @@ async function handler(ctx) { }, }); - const data = getProperty(JSON.parse(response.data.match(/window.initialState=({.*})/)[1]), categories[category].key); + const data = getProperty(JSON.parse(response.data.match(/window.initialState=(\{.*\})/)[1]), categories[category].key); let items = data .slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10) @@ -95,7 +94,7 @@ async function handler(ctx) { }; }); - items = await Promise.all(items.map((item) => ProcessItem(item, cache.tryGet))); + items = await Promise.all(items.map((item) => ProcessItem(item))); return { title: `36氪 - ${categories[category].title}`, diff --git a/lib/routes/36kr/index.ts b/lib/routes/36kr/index.ts index 6c24a1d83726..604d9e721fef 100644 --- a/lib/routes/36kr/index.ts +++ b/lib/routes/36kr/index.ts @@ -1,7 +1,6 @@ import { load } from 'cheerio'; import type { Route } from '@/types'; -import cache from '@/utils/cache'; import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; @@ -48,7 +47,7 @@ async function handler(ctx) { const $ = load(response.data); - const data = JSON.parse(response.data.match(/"itemList":(\[.*?])/)[1]); + const data = JSON.parse(response.data.match(/"itemList":(\[.*?\])/)[1]); let items = data .slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 30) @@ -64,12 +63,12 @@ async function handler(ctx) { }; }); - if (!/^\/(search|newsflashes)/.test(path)) { - items = await Promise.all(items.map((item) => ProcessItem(item, cache.tryGet))); + if (!/^\/(?:search|newsflashes)/.test(path)) { + items = await Promise.all(items.map((item) => ProcessItem(item))); } return { - title: `36氪 - ${$('title').text().split('_')[0]}`, + title: `36氪 - ${$('title').text().split('_', 1)[0]}`, link: currentUrl, item: items, }; diff --git a/lib/routes/36kr/utils.ts b/lib/routes/36kr/utils.ts index 5149e1ae2864..918ee0971b12 100644 --- a/lib/routes/36kr/utils.ts +++ b/lib/routes/36kr/utils.ts @@ -7,11 +7,11 @@ import ofetch from '@/utils/ofetch'; export const rootUrl = 'https://www.36kr.com'; -export const ProcessItem = (item, tryGet) => - tryGet(item.link, async () => { +export const ProcessItem = (item) => + cache.tryGet(item.link, async () => { const detailResponse = await ofetch(item.link); - const cipherTextList = detailResponse.match(/{"state":"(.*)","isEncrypt":true}/) ?? []; + const cipherTextList = detailResponse.match(/\{"state":"(.*)","isEncrypt":true\}/) ?? []; if (cipherTextList.length === 0) { const $ = load(detailResponse); @@ -54,8 +54,8 @@ export const getWafTokenId = () => const _wafTokenId = tokenIdResponse.headers .getSetCookie() .find((cookie) => cookie.startsWith('_waftokenid=')) - ?.split(';')[0] - .split('=')[1]; + ?.split(';', 1)[0] + .split('=', 2)[1]; return _wafTokenId as string; }, diff --git a/lib/routes/3dmgame/game.ts b/lib/routes/3dmgame/game.ts index 888ce02f46be..c6ebcd8458cc 100644 --- a/lib/routes/3dmgame/game.ts +++ b/lib/routes/3dmgame/game.ts @@ -1,7 +1,6 @@ import { load } from 'cheerio'; import type { Route } from '@/types'; -import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; @@ -40,10 +39,10 @@ async function handler(ctx) { }; }); - const items = await Promise.all(list.map((item) => parseArticle(item, cache.tryGet))); + const items = await Promise.all(list.map((item) => parseArticle(item))); return { - title: $('head title').text().split('_')[0], + title: $('head title').text().split('_', 1)[0], description: $('head meta[name="Description"]').attr('content'), link: url, item: items, diff --git a/lib/routes/3dmgame/news-center.ts b/lib/routes/3dmgame/news-center.ts index d51e0c760928..8006f074c5df 100644 --- a/lib/routes/3dmgame/news-center.ts +++ b/lib/routes/3dmgame/news-center.ts @@ -1,7 +1,6 @@ import { load } from 'cheerio'; import type { Route } from '@/types'; -import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; @@ -61,10 +60,10 @@ async function handler(ctx) { }; }); - const out = await Promise.all(list.map((item) => parseArticle(item, cache.tryGet))); + const out = await Promise.all(list.map((item) => parseArticle(item))); return { - title: '3DM - ' + $('title').text().split('_')[0], + title: '3DM - ' + $('title').text().split('_', 1)[0], description: $('meta[name="Description"]').attr('content'), link: url, item: out, diff --git a/lib/routes/3dmgame/utils.ts b/lib/routes/3dmgame/utils.ts index 37bda824243a..fff6bccaf315 100644 --- a/lib/routes/3dmgame/utils.ts +++ b/lib/routes/3dmgame/utils.ts @@ -1,17 +1,18 @@ import { load } from 'cheerio'; +import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; -const parseArticle = (item, tryGet) => - tryGet(item.link, async () => { +const parseArticle = (item) => + cache.tryGet(item.link, async () => { const { data: response } = await got(item.link); const $ = load(response); if (item.link.startsWith('https://dl.3dmgame.com/')) { const lis = $('.patchtop .lis'); - const [, category, pubDate, author] = lis.text().match(/补丁类型:(.*?)\n.*整理时间:(.*?)\n.*补丁制作:(.*?)\n/s); + const [, category, pubDate, author] = lis.text().match(/补丁类型:([^\n]*)\n.*整理时间:([^\n]*)\n.*补丁制作:([^\n]*)\n/s); item.description = lis.html() + $('.L_title').html() + $('.GmL_1').html(); item.category = category; diff --git a/lib/routes/3kns/index.tsx b/lib/routes/3kns/index.tsx index 7cab6030e233..eb3363bb8cfc 100644 --- a/lib/routes/3kns/index.tsx +++ b/lib/routes/3kns/index.tsx @@ -98,7 +98,11 @@ async function handler(ctx: Context): Promise { description: renderToString( baseUrl)} title={title} tid={$item.find('.jb-chakan').text().trim()} category={category} @@ -114,7 +118,7 @@ async function handler(ctx: Context): Promise { return { title: $('title').text(), - link: currentUrl.toString(), + link: currentUrl.href, allowEmpty: true, item: items, }; diff --git a/lib/routes/423down/index.ts b/lib/routes/423down/index.ts index c5fb36e757ac..84ad2ab49dcd 100644 --- a/lib/routes/423down/index.ts +++ b/lib/routes/423down/index.ts @@ -9,7 +9,7 @@ import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const { category = '' } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 18; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 18; const domain = '423down.com'; const rootUrl = `https://www.${domain}`; @@ -83,7 +83,7 @@ export const handler = async (ctx) => { item.title = title; item.description = description; - item.pubDate = parseDate($$('p.meta-info').contents().first().text().trim().split(/\s/)[0], 'YYYY-MM-DD'); + item.pubDate = parseDate($$('p.meta-info').contents().first().text().trim().split(/\s/, 1)[0], 'YYYY-MM-DD'); item.category = $$('p.meta-info a[rel="category tag"]') .toArray() .map((c) => $$(c).text()); diff --git a/lib/routes/4chan/utils.tsx b/lib/routes/4chan/utils.tsx index 726523378e9e..171079b2dc29 100644 --- a/lib/routes/4chan/utils.tsx +++ b/lib/routes/4chan/utils.tsx @@ -35,18 +35,14 @@ const processCatalog = ({ data, board, viewOptions }: { data: CatalogApiReturn; return false; } - if (viewOptions.maxReplies !== undefined && replies > viewOptions.maxReplies) { - return false; - } - - return true; + return viewOptions.maxReplies === undefined || replies <= viewOptions.maxReplies; }) .map((thread) => ({ author: `${thread.name} ${thread.trip ?? thread.no}`, description: renderToString(renderPost({ post: thread, board, viewOptions })), link: `https://boards.4chan.org/${board}/thread/${thread.no}`, pubDate: parseDate(thread.time * 1000), - title: thread.sub ?? sanitizeHtml(thread.com?.split('
')[0] ?? '', { allowedTags: [] }), + title: thread.sub ?? sanitizeHtml(thread.com?.split('
', 1)[0] ?? '', { allowedTags: [] }), })); }; diff --git a/lib/routes/4gamers/category.ts b/lib/routes/4gamers/category.ts index e2584f699402..da6c33f73aa7 100644 --- a/lib/routes/4gamers/category.ts +++ b/lib/routes/4gamers/category.ts @@ -35,7 +35,7 @@ async function handler(ctx) { let categoryName = '最新消息'; if (!isLatest) { - const categories = await getCategories(cache.tryGet); + const categories = await getCategories(); categoryName = categories.find((c) => c.id === Number.parseInt(category)).name; } diff --git a/lib/routes/4gamers/utils.tsx b/lib/routes/4gamers/utils.tsx index ca7ea1404d9d..a3358f3b465a 100644 --- a/lib/routes/4gamers/utils.tsx +++ b/lib/routes/4gamers/utils.tsx @@ -2,11 +2,12 @@ import { raw } from 'hono/html'; import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; +import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -const getCategories = (tryGet) => - tryGet('4gamers:categories', async () => { +const getCategories = () => + cache.tryGet('4gamers:categories', async () => { const { data: response } = await got('https://www.4gamers.com.tw/site/api/news/category'); return response.data.map((category) => ({ 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..f4f811c68fd3 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('; '); }); @@ -182,9 +182,9 @@ async function handler(ctx) { const title = l.contents().first().text(); const link = l.next().prop('href') ?? l.nextUntil('a').next().prop('href'); - item.enclosure_url = item.enclosure_url ?? link; - item.enclosure_type = item.enclosure_type ?? 'application/x-bittorrent'; - item.enclosure_title = item.enclosure_title ?? title; + item.enclosure_url ??= link; + item.enclosure_type ??= 'application/x-bittorrent'; + item.enclosure_title ??= title; return { title, @@ -237,7 +237,7 @@ async function handler(ctx) { info: $$('div.nex_drama_sums').html(), links, }); - item.pubDate = timezone(parseDate(pubDate, 'YYYY-M-D HH:mm:ss'), +8); + item.pubDate = timezone(parseDate(pubDate, 'YYYY-M-D HH:mm:ss'), 8); item.category = Object.values(mergedDetails) .flatMap((c) => c.split(/\s/)) .filter(Boolean); 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/50forum/zhuanjia.ts b/lib/routes/50forum/zhuanjia.ts index c5687eb29bb4..4251b4c60fc9 100644 --- a/lib/routes/50forum/zhuanjia.ts +++ b/lib/routes/50forum/zhuanjia.ts @@ -46,7 +46,7 @@ async function handler() { return { title: keyword[1], author: keyword[2], - pubDate: timezone(parseDate(keyword[3], 'YYYY-MM-DD'), +8), + pubDate: timezone(parseDate(keyword[3], 'YYYY-MM-DD'), 8), link, }; }); diff --git a/lib/routes/51cto/recommend.ts b/lib/routes/51cto/recommend.ts index 1a5c21871351..ee8b1b85bf69 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); @@ -53,7 +53,7 @@ async function getFullcontent(item, cookie = '') { return { title: item.title, link: item.url, - pubDate: parseDate(item.pubdate, +8), + pubDate: parseDate(item.pubdate, 8), description: fullContent || item.abstract, // Return item.abstract if fullContent is null }; } @@ -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 = {}, timestamp: number, token: string) => { payload.timestamp = timestamp; payload.token = token; - const sortedParams = Object.keys(payload).toSorted(); + const sortedParams = Object.keys(payload).toSorted((a, b) => a.localeCompare(b)); return md5(md5(requestPath) + md5(sortedParams + md5(token) + timestamp)); }; diff --git a/lib/routes/52hrtt/index.ts b/lib/routes/52hrtt/index.ts index 76cc9a6b3a4f..ece291d5bcc7 100644 --- a/lib/routes/52hrtt/index.ts +++ b/lib/routes/52hrtt/index.ts @@ -52,7 +52,7 @@ async function handler(ctx) { .map((item) => ({ title: item.infoTitle, author: item.quoteFrom, - pubDate: timezone(parseDate(item.infoStartTime), +8), + pubDate: timezone(parseDate(item.infoStartTime), 8), link: `${rootUrl}/${area}/n/w/info/${item.infoCentreId}`, })); diff --git a/lib/routes/52hrtt/symposium.ts b/lib/routes/52hrtt/symposium.ts index b8c568479ce8..d2ffd22f1896 100644 --- a/lib/routes/52hrtt/symposium.ts +++ b/lib/routes/52hrtt/symposium.ts @@ -62,7 +62,7 @@ async function handler(ctx) { .map((item) => ({ title: item.infoTitle, author: item.quoteFrom, - pubDate: timezone(parseDate(item.infoStartTime), +8), + pubDate: timezone(parseDate(item.infoStartTime), 8), link: `${rootUrl}/global/n/w/info/${item.infoCentreId}`, })); diff --git a/lib/routes/56kog/class.ts b/lib/routes/56kog/class.ts index 5c4394639680..ab97dccfd890 100644 --- a/lib/routes/56kog/class.ts +++ b/lib/routes/56kog/class.ts @@ -1,5 +1,4 @@ import type { Route } from '@/types'; -import cache from '@/utils/cache'; import { fetchItems, rootUrl } from './util'; @@ -30,9 +29,9 @@ export const route: Route = { async function handler(ctx) { const { category = '1_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(`class/${category}.html`, rootUrl).href; - return await fetchItems(limit, currentUrl, cache.tryGet); + return await fetchItems(limit, currentUrl); } diff --git a/lib/routes/56kog/top.ts b/lib/routes/56kog/top.ts index f9176fd65542..13605e02c500 100644 --- a/lib/routes/56kog/top.ts +++ b/lib/routes/56kog/top.ts @@ -1,5 +1,4 @@ import type { Route } from '@/types'; -import cache from '@/utils/cache'; import { fetchItems, rootUrl } from './util'; @@ -26,9 +25,9 @@ export const route: Route = { async function handler(ctx) { const { category = 'weekvisit' } = 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(`top/${category.split(/_/)[0]}_1.html`, rootUrl).href; + const currentUrl = new URL(`top/${category.split(/_/, 1)[0]}_1.html`, rootUrl).href; - return await fetchItems(limit, currentUrl, cache.tryGet); + return await fetchItems(limit, currentUrl); } diff --git a/lib/routes/56kog/util.tsx b/lib/routes/56kog/util.tsx index 0748d86f1bcc..985c311179b8 100644 --- a/lib/routes/56kog/util.tsx +++ b/lib/routes/56kog/util.tsx @@ -2,13 +2,14 @@ import { load } from 'cheerio'; import { renderToString } from 'hono/jsx/dom/server'; import iconv from 'iconv-lite'; +import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; const rootUrl = 'https://www.56kog.com'; -const fetchItems = async (limit, currentUrl, tryGet) => { +const fetchItems = async (limit, currentUrl) => { const { data: response } = await got(currentUrl, { responseType: 'buffer', }); @@ -31,7 +32,7 @@ const fetchItems = async (limit, currentUrl, tryGet) => { items = await Promise.all( items.map((item) => - tryGet(item.link, async () => { + cache.tryGet(item.link, async () => { try { const { data: detailResponse } = await got(item.link, { responseType: 'buffer', @@ -46,7 +47,7 @@ const fetchItems = async (limit, currentUrl, tryGet) => { const as = detail.find('a'); return { - label: detail.find('span.c-l-depths').text().split(/:/)[0], + label: detail.find('span.c-l-depths').text().split(/:/, 1)[0], value: as.length === 0 ? content( @@ -79,7 +80,7 @@ const fetchItems = async (limit, currentUrl, tryGet) => { item.author = details.find((detail) => detail.label === '作者').value; item.category = [details.find((detail) => detail.label === '状态').value, details.find((detail) => detail.label === '类型').value.text].filter(Boolean); item.guid = `56kog-${item.link.match(/\/(\d+)\.html$/)[1]}#${pubDate}`; - item.pubDate = timezone(parseDate(pubDate), +8); + item.pubDate = timezone(parseDate(pubDate), 8); } catch { // no-empty } @@ -121,7 +122,7 @@ const renderDescription = ({ images, details }: { images?: Array<{ src?: string; {details.map((detail, index) => ( {detail.label} - {detail.value?.href && detail.value?.text ? {detail.value.text} : detail.value} + {detail.value?.href && detail.value.text ? {detail.value.text} : detail.value} ))} diff --git a/lib/routes/69shu/article.ts b/lib/routes/69shu/article.ts index bc13b6c30ce7..b01f21322338 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,8 @@ const parseObject = (reg: RegExp, str: string): Record => { const obj = {}; const match = reg.exec(str); if (match) { - for (const line of match[0].matchAll(/(\w+):\s?["']?([\S\s]+?)["']?[\n,}]/g)) { + const matchedLines = match[0].matchAll(/(\w+):\s?["']?([\s\S]+?)["']?[\n,}]/g); + for (const line of matchedLines) { obj[line[1]] = line[2]; } } @@ -86,14 +87,17 @@ const decrypt = (txt: string, articleid: string, chapterid: string, decryptionMa const lineMap = {}; const articleKey = Number(articleid) + 3_061_711; const chapterKey = Number(chapterid) + 3_421_001; - for (const key of Object.keys(decryptionMap)) { - lineMap[(Number(key) ^ chapterKey) - articleKey] = (Number(decryptionMap[key]) ^ chapterKey) - articleKey; + for (const [key, value] of Object.entries(decryptionMap)) { + lineMap[(Number(key) ^ chapterKey) - articleKey] = (Number(value) ^ chapterKey) - articleKey; } return txt - .replaceAll(/\u2003|\n/g, '') + .replaceAll(/\u{2003}|\n/gu, '') .split('

') - .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..fd6123acce6b 100644 --- a/lib/routes/6park/index.ts +++ b/lib/routes/6park/index.ts @@ -66,8 +66,8 @@ async function handler(ctx) { const content = load(detailResponse.data); item.title = content('title').text().replace(' -6park.com', ''); - item.author = detailResponse.data.match(/送交者: .*>(.*)<.*\[/)[1]; - item.pubDate = timezone(parseDate(detailResponse.data.match(/于 (.*) 已读/)[1], 'YYYY-MM-DD h:m'), +8); + 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() .replaceAll('

', '') diff --git a/lib/routes/6park/news.ts b/lib/routes/6park/news.ts index 2050185e5d8e..c6d53cb59dfc 100644 --- a/lib/routes/6park/news.ts +++ b/lib/routes/6park/news.ts @@ -76,11 +76,11 @@ 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(); - item.pubDate = timezone(parseDate(matches[2], 'YYYY-MM-DD h:m'), +8); + item.pubDate = timezone(parseDate(matches[2], 'YYYY-MM-DD h:m'), 8); item.description = content('#shownewsc').html().replaceAll('

', ''); } catch { // no-empty 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/6v123/utils.ts b/lib/routes/6v123/utils.ts index f4e8bab9096b..7ea686a78d3d 100644 --- a/lib/routes/6v123/utils.ts +++ b/lib/routes/6v123/utils.ts @@ -42,7 +42,7 @@ export async function processItems(ctx, baseURL, exclude) { list.map((item) => { const link = $(item).find('a'); const href = link.attr('href'); - const pubDate = timezone(parseDate($(item).find('span').text().replaceAll(/[[\]]/g, ''), 'MM-DD'), +8); + const pubDate = timezone(parseDate($(item).find('span').text().replaceAll(/[[\]]/g, ''), 'MM-DD'), 8); const text = link.text(); if (href === undefined) { diff --git a/lib/routes/78dm/index.ts b/lib/routes/78dm/index.ts index 7a7be12bbae0..fdebe8826419 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; @@ -49,7 +49,7 @@ export const handler = async (ctx) => { return { title, description, - pubDate: pubDate && /\d{4}(?:\.\d{2}){2}\s\d{2}:\d{2}/.test(pubDate) ? timezone(parseDate(pubDate, 'YYYY.MM.DD HH:mm'), +8) : undefined, + pubDate: pubDate && /\d{4}(?:\.\d{2}){2}\s\d{2}:\d{2}/.test(pubDate) ? timezone(parseDate(pubDate, 'YYYY.MM.DD HH:mm'), 8) : undefined, link: href?.startsWith('//') ? `https:${href}` : href, category: [ ...new Set([ @@ -104,7 +104,7 @@ export const handler = async (ctx) => { item.title = title; item.description = description; - item.pubDate = timezone(parseDate($$('p.push-time').text().split(/:/).pop()), +8); + item.pubDate = timezone(parseDate($$('p.push-time').text().split(/:/).pop()), 8); item.author = $$('a.push-username').contents().first().text(); item.content = { html: description, diff --git a/lib/routes/81/81rc/index.ts b/lib/routes/81/81rc/index.ts index e3687a4e0708..18bdf4f9aa18 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; @@ -27,7 +27,7 @@ export const handler = async (ctx) => { return { title: item.find('a').text(), - pubDate: timezone(parseDate(item.find('span').text()), +8), + pubDate: timezone(parseDate(item.find('span').text()), 8), link: item.find('a').prop('href'), language, }; @@ -44,7 +44,7 @@ export const handler = async (ctx) => { item.title = $$('h2').text(); item.description = description; - item.pubDate = timezone(parseDate($$('div.time span').last().text()), +8); + item.pubDate = timezone(parseDate($$('div.time span').last().text()), 8); item.author = $$('div.time span').first().text(); item.content = { html: description, diff --git a/lib/routes/8264/list.tsx b/lib/routes/8264/list.tsx index adcae9585e46..3a752f13ee8a 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; @@ -145,7 +145,7 @@ async function handler(ctx) { item.category = content('div.fl_dh a, div.site a') .toArray() .map((c) => content(c).text().trim()); - item.pubDate = timezone(parseDate(pubDate, ['YYYY-MM-DD HH:mm', 'YYYY-M-D HH:mm']), +8); + item.pubDate = timezone(parseDate(pubDate, ['YYYY-MM-DD HH:mm', 'YYYY-M-D HH:mm']), 8); return item; }) 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..eefa7f3002a9 100644 --- a/lib/routes/9to5/subsite.ts +++ b/lib/routes/9to5/subsite.ts @@ -41,16 +41,18 @@ async function handler(ctx) { } if (ctx.req.param('tag')) { - link = `${link}/guides/${ctx.req.param('tag')}/feed/`; + link += `/guides/${ctx.req.param('tag')}/feed/`; title = `${ctx.req.param('tag')} | ${title}`; } else { - link = `${link}/feed/`; + link += '/feed/'; } 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..5259262cbc18 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; @@ -43,7 +43,7 @@ export const handler = async (ctx) => { ] : undefined, }), - pubDate: timezone(parseDate(item.find('div.a9-rich-card-list_infos').text()), +8), + pubDate: timezone(parseDate(item.find('div.a9-rich-card-list_infos').text()), 8), language, }; }); @@ -95,7 +95,7 @@ export const handler = async (ctx) => { .match(/发表于 (\d+-\d+-\d+ \d+:\d+)/)?.[1] ?? $$('span.c-article-main_content-intro-item').first().text(), ['YYYY-M-D HH:mm', 'YYYY-MM-DD HH:mm'] ), - +8 + 8 ); item.language = language; 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..dbcd8dfc38b8 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,14 +49,14 @@ 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); const $ = load(currentResponse); - documentId = documentId ?? $('div[data-uri^="coremedia://collection/"]').first().prop('data-uri').split(/\//).pop(); + documentId ??= $('div[data-uri^="coremedia://collection/"]').first().prop('data-uri').split(/\//).pop(); const response = await ofetch(apiUrl, { query: { @@ -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..77976aa5630b 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; @@ -44,7 +44,7 @@ async function handler(ctx) { return { title: item.prop('title'), link: new URL(item.prop('href'), rootUrl).href, - pubDate: timezone(parseDate(item.find('span[title]').prop('title')), +8), + pubDate: timezone(parseDate(item.find('span[title]').prop('title')), 8), }; }); 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..096685cee854 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; @@ -81,14 +81,14 @@ export const handler = async (ctx: Context): Promise => { const processedItem: DataItem = { title, description, - pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : item.pubDate, + pubDate: pubDateStr ? timezone(parseDate(pubDateStr), 8) : item.pubDate, category: categories, author: authors, content: { html: description, text: description, }, - updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : item.updated, + updated: upDatedStr ? timezone(parseDate(upDatedStr), 8) : item.updated, language, }; diff --git a/lib/routes/adquan/index.ts b/lib/routes/adquan/index.ts index 5a11ff58e430..a739a7cf4106 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; @@ -81,14 +81,14 @@ export const handler = async (ctx: Context): Promise => { const processedItem: DataItem = { title, description, - pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : item.pubDate, + pubDate: pubDateStr ? timezone(parseDate(pubDateStr), 8) : item.pubDate, category: categories, author: authors, content: { html: description, text: description, }, - updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : item.updated, + updated: upDatedStr ? timezone(parseDate(upDatedStr), 8) : item.updated, language, }; 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..de2d4248ad88 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; @@ -72,7 +72,7 @@ export const handler = async (ctx) => { item.title = title; item.description = description; - item.pubDate = timezone(parseDate($$('meta[name="publishdate"]').prop('content')), +8); + item.pubDate = timezone(parseDate($$('meta[name="publishdate"]').prop('content')), 8); item.author = $$('meta[name="author"]').prop('content') || $$('meta[name="source"]').prop('content'); item.content = { html: description, diff --git a/lib/routes/ahjzu/news.ts b/lib/routes/ahjzu/news.ts index f3f663645702..4ee5ad425ff4 100644 --- a/lib/routes/ahjzu/news.ts +++ b/lib/routes/ahjzu/news.ts @@ -54,7 +54,7 @@ async function handler() { return { title: item.find('a').attr('title'), link, - pubDate: timezone(parseDate(date), +8), + pubDate: timezone(parseDate(date), 8), }; }); 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..e9a445caa4c4 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; @@ -22,7 +22,7 @@ export const handler = async (ctx) => { data: { results: apiTagProcs }, } = await ofetch(apiTagProcUrl, { query: { - ...(id ? { tag: id } : {}), + ...(id && { tag: id }), page: 1, pagesize: 20, f: filter, diff --git a/lib/routes/aibase/util.tsx b/lib/routes/aibase/util.tsx index 19364d0d303f..72724c27033c 100644 --- a/lib/routes/aibase/util.tsx +++ b/lib/routes/aibase/util.tsx @@ -91,7 +91,7 @@ const processItems = (items: any[]): any[] => return { title, description, - pubDate: timezone(parseDate(item.addtime), +8), + pubDate: timezone(parseDate(item.addtime), 8), link: new URL(`tool/${item.zurl}`, rootUrl).href, category: [...new Set([...strToArray(item.categories), ...strToArray(item.tags), item.catname, item.procattrname, item.procformname, item.proctypename])].filter(Boolean), guid, 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..5372bfb3d700 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, @@ -61,4 +61,5 @@ const handler = async (ctx) => { allowEmpty: true, }; }; +// TODO: missing route export export default handler; 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..c2391f9132ab 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'], @@ -37,9 +37,7 @@ async function handler(ctx) { if (status === 'best') { title = titleBest; table = `${tableBest}
`; - } - - if (status === 'worsest') { + } else if (status === 'worsest') { title = titleWorst; table = `${tableWorst}
`; } diff --git a/lib/routes/aisixiang/column.ts b/lib/routes/aisixiang/column.ts index ba0d9f5363dd..a3d9607f5ba0 100644 --- a/lib/routes/aisixiang/column.ts +++ b/lib/routes/aisixiang/column.ts @@ -1,7 +1,6 @@ import { load } from 'cheerio'; import type { Route } from '@/types'; -import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; @@ -28,7 +27,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,13 +48,13 @@ async function handler(ctx) { return { title: a.text(), link: new URL(a.prop('href'), rootUrl).href, - author: a.text().split(':')[0], - pubDate: timezone(parseDate(item.find('span').text()), +8), + author: a.text().split(':', 1)[0], + pubDate: timezone(parseDate(item.find('span').text()), 8), }; }); return { - item: await ProcessFeed(limit, cache.tryGet, items), + item: await ProcessFeed(limit, items), title: `爱思想 - ${title}`, link: currentUrl, description: $('div.tips').text(), diff --git a/lib/routes/aisixiang/thinktank.ts b/lib/routes/aisixiang/thinktank.ts index d32d70799685..427e27ae401f 100644 --- a/lib/routes/aisixiang/thinktank.ts +++ b/lib/routes/aisixiang/thinktank.ts @@ -2,7 +2,6 @@ import { load } from 'cheerio'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; -import cache from '@/utils/cache'; import got from '@/utils/got'; import { ossUrl, ProcessFeed, rootUrl } from './utils'; @@ -29,7 +28,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; @@ -62,7 +61,7 @@ async function handler(ctx) { }); return { - item: await ProcessFeed(limit, cache.tryGet, items), + item: await ProcessFeed(limit, items), title: `爱思想 - ${title}`, link: currentUrl, description: $('div.thinktank-author-description-box p').text(), diff --git a/lib/routes/aisixiang/toplist.ts b/lib/routes/aisixiang/toplist.ts index 3599fe035e94..ede852f5e190 100644 --- a/lib/routes/aisixiang/toplist.ts +++ b/lib/routes/aisixiang/toplist.ts @@ -1,7 +1,6 @@ import { load } from 'cheerio'; import type { Route } from '@/types'; -import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; @@ -19,7 +18,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 +26,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) @@ -46,7 +45,7 @@ async function handler(ctx) { }); return { - item: await ProcessFeed(limit, cache.tryGet, items), + item: await ProcessFeed(limit, items), title: `爱思想 - ${title}`, link: currentUrl, language: 'zh-cn', diff --git a/lib/routes/aisixiang/utils.ts b/lib/routes/aisixiang/utils.ts index 94ba3e62ecfd..befc488ba685 100644 --- a/lib/routes/aisixiang/utils.ts +++ b/lib/routes/aisixiang/utils.ts @@ -1,5 +1,6 @@ import { load } from 'cheerio'; +import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; @@ -7,10 +8,10 @@ import timezone from '@/utils/timezone'; const ossUrl = 'https://oss.aisixiang.com'; const rootUrl = 'https://www.aisixiang.com'; -const ProcessFeed = (limit, tryGet, items) => +const ProcessFeed = (limit, items) => Promise.all( items.slice(0, limit).map((item) => - tryGet(item.link, async () => { + cache.tryGet(item.link, async () => { const { data: detailResponse } = await got(item.link); const content = load(detailResponse); @@ -28,9 +29,9 @@ const ProcessFeed = (limit, tryGet, items) => .find('u') .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.pubDate = timezone(parseDate(content('div.info').text().split('时间:').pop()), 8); + 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..b08713dd4664 100644 --- a/lib/routes/aisixiang/zhuanti.ts +++ b/lib/routes/aisixiang/zhuanti.ts @@ -1,7 +1,6 @@ import { load } from 'cheerio'; import type { Route } from '@/types'; -import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; @@ -31,7 +30,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,13 +51,13 @@ async function handler(ctx) { return { title: a.text(), link: new URL(a.prop('href'), rootUrl).href, - author: a.text().split(':')[0], - pubDate: timezone(parseDate(item.find('span').text()), +8), + author: a.text().split(':', 1)[0], + pubDate: timezone(parseDate(item.find('span').text()), 8), }; }); return { - item: await ProcessFeed(limit, cache.tryGet, items), + item: await ProcessFeed(limit, items), title: `爱思想 - ${title}`, link: currentUrl, description: $('div.tips p').text(), diff --git a/lib/routes/ali213/news.ts b/lib/routes/ali213/news.ts index f19844e862d4..bdb7b6442844 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/aliresearch/information.ts b/lib/routes/aliresearch/information.ts index 2baf52a7c2d4..79df0c446215 100644 --- a/lib/routes/aliresearch/information.ts +++ b/lib/routes/aliresearch/information.ts @@ -52,7 +52,7 @@ async function handler(ctx) { let items = response.data.data.slice(0, limit).map((item) => ({ title: item.articleCode, author: item.author, - pubDate: timezone(parseDate(item.gmtCreated), +8), + pubDate: timezone(parseDate(item.gmtCreated), 8), link: `${rootUrl}/ch/information/informationdetails?articleCode=${item.articleCode}`, })); diff --git a/lib/routes/aliyun/notice.ts b/lib/routes/aliyun/notice.ts index f86cb0b642eb..027bf7452c13 100644 --- a/lib/routes/aliyun/notice.ts +++ b/lib/routes/aliyun/notice.ts @@ -55,7 +55,7 @@ async function handler(ctx) { const title = element.find('a').text().trim(); const link = 'https://help.aliyun.com' + element.find('a').attr('href').trim(); const date = element.find('.y-right').text(); - const pubDate = timezone(parseDate(date), +8); + const pubDate = timezone(parseDate(date), 8); return { title, description: '', 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..2d18beac5493 100644 --- a/lib/routes/ally/rail.ts +++ b/lib/routes/ally/rail.ts @@ -47,7 +47,7 @@ async function handler(ctx) { const linkText = $(link).text(); title = title ? `${title} - ${linkText}` : linkText; } - title = title || (category && topic ? `${category} - ${topic}` : category) || '首页'; + title ||= (category && topic ? `${category} - ${topic}` : category) || '首页'; let links = [ // list page: http://rail.ally.net.cn/html/lujuzixun/ $('.left .hynewsO h2 a').toArray(), @@ -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!); } } @@ -106,7 +106,7 @@ async function handler(ctx) { let innerHtml; if (child.name === 'div') { innerHtml = $child.html(); - innerHtml = innerHtml && innerHtml.trim(); + innerHtml &&= innerHtml.trim(); description += !innerHtml || innerHtml === ' ' ? (description ? '
' : '') : innerHtml; } else { // bare text node or something else 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, language: string) { const descEl = a.find('p').first(); const description = descEl.text().trim(); - const dateMatch = language === 'fr' ? description.match(/(\d{1,2} [a-zéû]+[.]? \d{4})/i) : description.match(/([A-Z][a-z]+[.]? \d{1,2}, \d{4})/); + const dateMatch = language === 'fr' ? description.match(/(\d{1,2} [a-zéû]+\.? \d{4})/i) : description.match(/([A-Z][a-z]+\.? \d{1,2}, \d{4})/); const pubDateStr = dateMatch ? dateMatch[1].trim() : ''; const pubDate = parseDate(pubDateStr); diff --git a/lib/routes/amazfitwatchfaces/index.ts b/lib/routes/amazfitwatchfaces/index.ts index 1806b8dc0866..a4a54df79695 100644 --- a/lib/routes/amazfitwatchfaces/index.ts +++ b/lib/routes/amazfitwatchfaces/index.ts @@ -13,7 +13,7 @@ import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { device, sort, searchParams } = 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://amazfitwatchfaces.com'; const targetUrl: string = new URL(`${device}/${sort}${searchParams ? `?${searchParams}` : ''}`, baseUrl).href; diff --git a/lib/routes/annualreviews/index.ts b/lib/routes/annualreviews/index.ts index 1abce26b925f..b4a223bf6bd7 100644 --- a/lib/routes/annualreviews/index.ts +++ b/lib/routes/annualreviews/index.ts @@ -59,7 +59,7 @@ async function handler(ctx) { doi, guid: doi, title: item.find('title').text(), - link: item.find('link').attr('href').split('?')[0], + link: item.find('link').attr('href').split('?', 1)[0], description: item.find('content').text(), pubDate: parseDate(item.find('published').text()), author: item diff --git a/lib/routes/anquanke/category.ts b/lib/routes/anquanke/category.ts index 2da6a9349ee0..90911962e954 100644 --- a/lib/routes/anquanke/category.ts +++ b/lib/routes/anquanke/category.ts @@ -48,7 +48,7 @@ async function handler(ctx) { return content('#js-article').html(); }) : item.desc, - pubDate: timezone(parseDate(item.date), +8), + pubDate: timezone(parseDate(item.date), 8), link: art_url, author: item.author.nickname, }; diff --git a/lib/routes/anquanke/vul.ts b/lib/routes/anquanke/vul.ts index 1ecdf681abcc..7a30a7f385ef 100644 --- a/lib/routes/anquanke/vul.ts +++ b/lib/routes/anquanke/vul.ts @@ -34,4 +34,5 @@ const handler = async () => { }; }; +// TODO: missing route export export default handler; diff --git a/lib/routes/anthropic/engineering.ts b/lib/routes/anthropic/engineering.ts index a07974b14895..acb0435dfd5d 100644 --- a/lib/routes/anthropic/engineering.ts +++ b/lib/routes/anthropic/engineering.ts @@ -26,7 +26,7 @@ async function handler(ctx) { const link = `${baseUrl}/engineering`; const response = await ofetch(link); const $ = load(response); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const list: DataItem[] = $('a[class*="cardLink"]') .toArray() diff --git a/lib/routes/anthropic/news.ts b/lib/routes/anthropic/news.ts index 9708029ef637..52c05e8fc836 100644 --- a/lib/routes/anthropic/news.ts +++ b/lib/routes/anthropic/news.ts @@ -25,7 +25,7 @@ async function handler(ctx) { const link = 'https://www.anthropic.com/news'; const response = await ofetch(link); const $ = load(response); - 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 list: DataItem[] = $('[class^="PublicationList-module-scss-module__"][class$="__list"] a') .toArray() diff --git a/lib/routes/anthropic/research.ts b/lib/routes/anthropic/research.ts index cfba01932e4d..9a7baa33b66d 100644 --- a/lib/routes/anthropic/research.ts +++ b/lib/routes/anthropic/research.ts @@ -35,9 +35,8 @@ async function handler() { const text = $e.text(); const match = regexp.exec(text); if (match) { - let data; try { - data = JSON.parse(match[1]); + const data = JSON.parse(match[1]); if (Array.isArray(data) && data.length === 2 && data[0] === 1) { textList.push(data[1]); } @@ -47,7 +46,7 @@ async function handler() { } } - const partRegex = /^([0-9a-zA-Z]+):([0-9a-zA-Z]+)?(\[.*)$/; + const partRegex = /^([0-9a-z]+):([0-9a-z]+)?(\[.*)$/i; const fd = textList .join('') .split('\n') diff --git a/lib/routes/anytxt/release-notes.ts b/lib/routes/anytxt/release-notes.ts index 32901623dfa9..6b4535f86359 100644 --- a/lib/routes/anytxt/release-notes.ts +++ b/lib/routes/anytxt/release-notes.ts @@ -9,7 +9,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') ?? '30', 10); + const limit = Number(ctx.req.query('limit') ?? '30'); const baseUrl = 'https://anytxt.net'; const targetUrl: string = new URL('download/', baseUrl).href; @@ -28,7 +28,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $el.text(); const description: string | undefined = $el.next().html() ?? ''; - const pubDateStr: string | undefined = title.split(/\s/)[0]; + const pubDateStr: string | undefined = title.split(/\s/, 1)[0]; const linkUrl: string | undefined = targetUrl; const upDatedStr: string | undefined = pubDateStr; diff --git a/lib/routes/apiseven/blog.ts b/lib/routes/apiseven/blog.ts index eb53f4bcdaae..af685ff6fcf5 100644 --- a/lib/routes/apiseven/blog.ts +++ b/lib/routes/apiseven/blog.ts @@ -19,7 +19,7 @@ async function getArticles() { return json.props.pageProps.list.map((item) => ({ title: item.title, link: 'https://www.apiseven.com' + item.slug, - pubDate: timezone(parseDate(item.published_at), +8), + pubDate: timezone(parseDate(item.published_at), 8), category: item.tags, })); } diff --git a/lib/routes/apkpure/versions.ts b/lib/routes/apkpure/versions.ts index 911f05d2ae65..bce6bb781d1e 100644 --- a/lib/routes/apkpure/versions.ts +++ b/lib/routes/apkpure/versions.ts @@ -28,11 +28,11 @@ async function handler(ctx) { const baseUrl = 'https://apkpure.com'; const link = `${baseUrl}/${region}/${pkg}/versions`; - 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(); }); logger.http(`Requesting ${link}`); await page.goto(link, { @@ -40,7 +40,7 @@ async function handler(ctx) { }); const r = await page.evaluate(() => document.documentElement.innerHTML); - await browser.close(); + await context.close(); const $ = load(r); const img = new URL($('.ver-top img').attr('src')); diff --git a/lib/routes/apnews/mobile-api.ts b/lib/routes/apnews/mobile-api.ts index 3c894ca3a36e..7263daa9dc03 100644 --- a/lib/routes/apnews/mobile-api.ts +++ b/lib/routes/apnews/mobile-api.ts @@ -10,12 +10,12 @@ import { fetchArticle } from './utils'; export const route: Route = { path: '/mobile/:path{.+}?', categories: ['traditional-media'], - example: '/apnews/mobile/ap-top-news', + example: '/apnews/mobile', view: ViewType.Articles, parameters: { path: { description: 'Corresponding path from AP News website', - default: 'ap-top-news', + default: '/', }, }, features: { @@ -37,7 +37,7 @@ export const route: Route = { }; async function handler(ctx) { - const path = ctx.req.param('path') ? `/${ctx.req.param('path')}` : '/hub/ap-top-news'; + const path = ctx.req.param('path') ? `/${ctx.req.param('path')}` : '/'; const apiRootUrl = 'https://apnews.com/graphql/delivery/ap/v1'; const res = await ofetch(apiRootUrl, { query: { @@ -72,20 +72,20 @@ async function handler(ctx) { description: e.description, guid: e.id, }; - } else if (e.__typename === 'VideoPlaylistItem') { + } + if (e.__typename === 'VideoPlaylistItem') { return { title: e.title, link: e.url, description: e.description, guid: e.contentId, }; - } else { - return; } + return; }) .filter(Boolean) .toSorted((a, b) => b.pubDate - a.pubDate) - .slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20); + .slice(0, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20); const items = ctx.req.query('fulltext') === 'true' ? await pMap(list, (item) => fetchArticle(item), { concurrency: 10 }) : list; diff --git a/lib/routes/apnews/sitemap.ts b/lib/routes/apnews/sitemap.ts index 48f68db257a2..3d61a0c0afcc 100644 --- a/lib/routes/apnews/sitemap.ts +++ b/lib/routes/apnews/sitemap.ts @@ -84,7 +84,7 @@ async function handler(ctx) { }) .filter((e) => Boolean(e.link) && !new URL(e.link).pathname.split('/').includes('hub')) .toSorted((a, b) => (a.pubDate && b.pubDate ? b.pubDate - a.pubDate : b.lastmod - a.lastmod)) - .slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20); + .slice(0, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20); const items = ctx.req.query('fulltext') === 'true' ? await pMap(list, (item) => fetchArticle(item), { concurrency: 20 }) : list; diff --git a/lib/routes/apnews/topics.ts b/lib/routes/apnews/topics.ts index 7c955205ce51..cdb8eeb9194a 100644 --- a/lib/routes/apnews/topics.ts +++ b/lib/routes/apnews/topics.ts @@ -10,7 +10,7 @@ import { fetchArticle, removeDuplicateByKey } from './utils'; const HOME_PAGE = 'https://apnews.com'; export const route: Route = { - path: ['/topics/:topic?', '/nav/:nav{.*}?'], + path: ['/topics/:topic?', '/nav/:nav{.+}?'], categories: ['traditional-media'], example: '/apnews/topics/apf-topnews', view: ViewType.Articles, @@ -41,7 +41,7 @@ export const route: Route = { async function handler(ctx) { const { topic = 'trending-news', nav = '' } = ctx.req.param(); - const useNav = ctx.req.routePath === '/apnews/nav/:nav{.*}?'; + const useNav = ctx.req.routePath === '/apnews/nav/:nav{.+}?'; const url = useNav ? `${HOME_PAGE}/${nav}` : `${HOME_PAGE}/hub/${topic}`; const response = await got(url); const $ = load(response.data); diff --git a/lib/routes/apnews/utils.ts b/lib/routes/apnews/utils.ts index 083c19fb9aa7..6225b23dec50 100644 --- a/lib/routes/apnews/utils.ts +++ b/lib/routes/apnews/utils.ts @@ -5,7 +5,7 @@ import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; export function removeDuplicateByKey(items, key: string) { - return [...new Map(items.map((x) => [x[key], x])).values()]; + return new Map(items.map((x) => [x[key], x])).values().toArray(); } export function fetchArticle(item) { @@ -25,9 +25,8 @@ export function fetchArticle(item) { author: gtmParsed.author, ...item, }; - } else { - return item; } + return item; } const rawLdjson = JSON.parse($('#link-ld-json').text()); let ldjson; @@ -45,23 +44,22 @@ export function fetchArticle(item) { description: $('div.RichTextStoryBody').html() || $(':is(.VideoLead, .VideoPage-pageSubHeading)').html(), category: [...(section ? [section] : []), ...(ldjson.keywords ?? [])], guid: $("meta[name='brightspot.contentId']").attr('content'), - author: ldjson.author?.map((e) => e.mainEntity), + author: ldjson.author, }; - } else { - // Live - ldjson = rawLdjson; + } + // Live + ldjson = rawLdjson; - const url = new URL(item.link); - const description = url.hash ? $(url.hash).parent().find('.LiveBlogPost-body').html() : ldjson.description; - const pubDate = url.hash ? parseDate(Number.parseInt($(url.hash).parent().attr('data-posted-date-timestamp'), 10)) : parseDate(ldjson.coverageStartTime); + const url = new URL(item.link); + const description = url.hash ? $(url.hash).parent().find('.LiveBlogPost-body').html() : ldjson.description; + const pubDate = url.hash ? parseDate(Number($(url.hash).parent().attr('data-posted-date-timestamp'))) : parseDate(ldjson.coverageStartTime); - return { - ...item, - category: ldjson.keywords, - pubDate, - description, - guid: $("meta[name='brightspot.contentId']").attr('content'), - }; - } + return { + ...item, + category: ldjson.keywords, + pubDate, + description, + guid: $("meta[name='brightspot.contentId']").attr('content'), + }; }); } diff --git a/lib/routes/app-center/release.tsx b/lib/routes/app-center/release.tsx index 65a235222a87..a41da5c3f164 100644 --- a/lib/routes/app-center/release.tsx +++ b/lib/routes/app-center/release.tsx @@ -158,7 +158,7 @@ async function handler(ctx) { const releaseResponse = await got(item.link); const releaseInfo = releaseResponse.data; - const userName = releaseInfo.owner.display_name; + const username = releaseInfo.owner.display_name; const appOS = releaseInfo.app_os; const shortVersion = releaseInfo.short_version; // will be an empty string for Windows const versionCode = releaseInfo.version; @@ -181,7 +181,7 @@ async function handler(ctx) { const appName = releaseInfo.app_display_name; const distributionGroupId = releaseInfo.distribution_group_id; const distributionGroupName = releaseInfo.distribution_groups.find((group) => group.id === distributionGroupId).display_name; - item._feed_title = `${appName} (${distributionGroupName}) for ${appOS} by ${userName} - App Center Releases`; + item._feed_title = `${appName} (${distributionGroupName}) for ${appOS} by ${username} - App Center Releases`; item._feed_icon = releaseInfo.app_icon_url; const version = shortVersion && versionCode ? `${shortVersion} (${versionCode})` : shortVersion || versionCode; @@ -193,7 +193,7 @@ async function handler(ctx) { (isExternalBuild ? '[External Build]' : '') + `Version ${version}`; item.link = link; // replace the link with the release page - item.author = userName; + item.author = username; item.description = renderDescription({ releaseDate, sizeInMBytes, diff --git a/lib/routes/app-sales/index.ts b/lib/routes/app-sales/index.ts index f0c772d3680d..ffbc99d8f2d6 100644 --- a/lib/routes/app-sales/index.ts +++ b/lib/routes/app-sales/index.ts @@ -10,7 +10,7 @@ import { baseUrl, fetchItems } from './util'; export const handler = async (ctx: Context): Promise => { const { category = 'highlights', country = 'us' } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '100', 10); + const limit = Number(ctx.req.query('limit') ?? '100'); const targetUrl: string = new URL(category.endsWith('/') ? category : `${category}/`, baseUrl).href; diff --git a/lib/routes/app-sales/mostwanted.ts b/lib/routes/app-sales/mostwanted.ts index 49995398da6f..90ea98339222 100644 --- a/lib/routes/app-sales/mostwanted.ts +++ b/lib/routes/app-sales/mostwanted.ts @@ -10,7 +10,7 @@ import { baseUrl, fetchItems } from './util'; export const handler = async (ctx: Context): Promise => { const { time = '24h', country = 'us' } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); + const limit = Number(ctx.req.query('limit') ?? '30'); const targetUrl: string = new URL('mostwanted/', baseUrl).href; diff --git a/lib/routes/apple/apps.ts b/lib/routes/apple/apps.ts index 402e4e8c6967..cf854838fb5c 100644 --- a/lib/routes/apple/apps.ts +++ b/lib/routes/apple/apps.ts @@ -79,7 +79,7 @@ async function handler(ctx) { } platform = undefined; - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 100; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 100; const rootUrl = 'https://apps.apple.com'; const currentUrl = new URL(`${country}/app/${id}`, rootUrl).href; @@ -129,8 +129,7 @@ async function handler(ctx) { image = platformAttribute.iconArtwork?.url?.replace('{w}x{h}{c}.{f}', '3000x3000bb.webp'); } else { title = appName; - for (const pid of Object.keys(platformAttributes)) { - const platformAttribute = platformAttributes[pid]; + for (const [pid, platformAttribute] of Object.entries(platformAttributes)) { items = [ ...items, ...platformAttribute.versionHistory.map((v) => ({ diff --git a/lib/routes/apple/newsroom.ts b/lib/routes/apple/newsroom.ts index ea915df082c4..949b2dce19c0 100644 --- a/lib/routes/apple/newsroom.ts +++ b/lib/routes/apple/newsroom.ts @@ -54,7 +54,7 @@ const fetchArticle = (item: Item & { link: string }) => }); async function handler(ctx) { - const limit = Math.max(Number.parseInt(ctx.req.query('limit') ?? '', 10) || defaultLimit, 1); + const limit = Number(ctx.req.query('limit')) || defaultLimit; const feedResponse = await ofetch(feedUrl, { parseResponse: (text) => text, }); diff --git a/lib/routes/apple/security-releases.ts b/lib/routes/apple/security-releases.ts index 9896caaa6fcf..54f533065322 100644 --- a/lib/routes/apple/security-releases.ts +++ b/lib/routes/apple/security-releases.ts @@ -13,7 +13,7 @@ import { renderDescription } from './templates/security-releases'; export const handler = async (ctx: Context): Promise => { const { language = 'en-us' } = 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://support.apple.com'; const targetUrl: string = new URL(`${language}/100100`, baseUrl).href; diff --git a/lib/routes/appstorrent/programs.tsx b/lib/routes/appstorrent/programs.tsx index ced480153000..009ca2d296dd 100644 --- a/lib/routes/appstorrent/programs.tsx +++ b/lib/routes/appstorrent/programs.tsx @@ -98,7 +98,7 @@ async function handler(ctx?: Context): Promise { return { title: $('title').text(), - link: currentUrl.toString(), + link: currentUrl, allowEmpty: true, item: items, }; diff --git a/lib/routes/aqara/news.ts b/lib/routes/aqara/news.ts index 898ea0dac62d..5de47f531771 100644 --- a/lib/routes/aqara/news.ts +++ b/lib/routes/aqara/news.ts @@ -13,7 +13,7 @@ export const route: Route = { }; async function handler(ctx) { - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 35; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 35; const rootUrl = 'https://www.aqara.cn'; const currentUrl = new URL('news', rootUrl).href; @@ -23,7 +23,7 @@ async function handler(ctx) { const $ = load(response); let items = response - .match(/(parm\.newsTitle[\S\s]*?arr\.push\(parm\))/g) + .match(/(parm\.newsTitle[\s\S]*?arr\.push\(parm\))/g) .slice(0, limit) .map((item) => ({ title: item.match(/parm\.newsTitle = '(.*?)'/)[1], @@ -47,7 +47,7 @@ async function handler(ctx) { ) ); - const icon = $('link[rel="shortcut icon"]').prop('href').split('?')[0]; + const icon = $('link[rel="shortcut icon"]').prop('href').split('?', 1)[0]; return { item: items, diff --git a/lib/routes/aqara/post.tsx b/lib/routes/aqara/post.tsx index 4861ba652fd4..d692b1eb6ac4 100644 --- a/lib/routes/aqara/post.tsx +++ b/lib/routes/aqara/post.tsx @@ -14,7 +14,7 @@ export const route: Route = { }; async function handler(ctx) { - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 50; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 50; const rootUrl = 'https://aqara.com'; const apiSlug = 'wp-json/wp/v2'; diff --git a/lib/routes/arcteryx/regear-new-arrivals.tsx b/lib/routes/arcteryx/regear-new-arrivals.tsx index 6785aa96c410..7394abf81ec0 100644 --- a/lib/routes/arcteryx/regear-new-arrivals.tsx +++ b/lib/routes/arcteryx/regear-new-arrivals.tsx @@ -42,7 +42,7 @@ async function handler() { const data = response.data; const $ = load(data); const contents = $('script:contains("window.__PRELOADED_STATE__")').text(); - const regex = /{.*}/; + const regex = /\{.*\}/; let items = JSON.parse(contents.match(regex)[0]).shop.items; items = items.filter((item) => item.availableSizes.length !== 0); diff --git a/lib/routes/artstation/user.ts b/lib/routes/artstation/user.ts index 49ef9bd275bd..bd088e835b67 100644 --- a/lib/routes/artstation/user.ts +++ b/lib/routes/artstation/user.ts @@ -45,7 +45,7 @@ async function handler(ctx) { method: 'POST', headers, }); - return tokenResponse.headers.getSetCookie()[0].split(';')[0].split('=')[1]; + return tokenResponse.headers.getSetCookie()[0].split(';', 1)[0].split('=', 2)[1]; }); const { data: userData } = await got(`https://www.artstation.com/users/${handle}/quick.json`, { diff --git a/lib/routes/asiafruitchina/categories.ts b/lib/routes/asiafruitchina/categories.ts index 2d73c4eaeb6c..15367ada3883 100644 --- a/lib/routes/asiafruitchina/categories.ts +++ b/lib/routes/asiafruitchina/categories.ts @@ -13,7 +13,7 @@ import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category = 'all' } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '10', 10); + const limit = Number(ctx.req.query('limit') ?? '10'); const baseUrl = 'https://asiafruitchina.net'; const targetUrl: string = new URL(`categories?gspx=${category}`, baseUrl).href; diff --git a/lib/routes/asiafruitchina/news.ts b/lib/routes/asiafruitchina/news.ts index 9ae47525121d..94a2477ac8fd 100644 --- a/lib/routes/asiafruitchina/news.ts +++ b/lib/routes/asiafruitchina/news.ts @@ -12,7 +12,7 @@ import { parseDate } from '@/utils/parse-date'; 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://asiafruitchina.net'; const targetUrl: string = new URL('category/news', baseUrl).href; diff --git a/lib/routes/asiantolick/index.ts b/lib/routes/asiantolick/index.ts index 8034e8d95099..920eedd65eb9 100644 --- a/lib/routes/asiantolick/index.ts +++ b/lib/routes/asiantolick/index.ts @@ -26,7 +26,7 @@ export const route: Route = { async function handler(ctx) { const category = ctx.req.param('category'); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 24; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 24; const rootUrl = 'https://asiantolick.com'; const apiUrl = new URL('ajax/buscar_posts.php', rootUrl).href; @@ -64,7 +64,7 @@ async function handler(ctx) { images: image ? [ { - src: image.prop('data-src').split(/\?/)[0], + src: image.prop('data-src').split(/\?/, 1)[0], alt: image.prop('alt'), }, ] @@ -115,7 +115,7 @@ async function handler(ctx) { $ = load(currentResponse); - const title = $('title').text().split(/-/)[0].trim(); + const title = $('title').text().split(/-/, 1)[0].trim(); const icon = $('link[rel="icon"]').first().prop('href'); return { diff --git a/lib/routes/asmr-200/index.tsx b/lib/routes/asmr-200/index.tsx index 7916bf48d02e..7a97941a7b85 100644 --- a/lib/routes/asmr-200/index.tsx +++ b/lib/routes/asmr-200/index.tsx @@ -85,7 +85,7 @@ export const route: Route = { image: each.mainCoverUrl, author: each.name, link: `https://asmr-200.com/work/${each.source_id}`, - pubDate: timezone(parseDate(each.release, 'YYYY-MM-DD'), +8), + pubDate: timezone(parseDate(each.release, 'YYYY-MM-DD'), 8), category, description: render(each, `https://asmr-200.com/work/${each.source_id}`), }; diff --git a/lib/routes/asus/bios.tsx b/lib/routes/asus/bios.tsx index f719b08717dc..b4bae402ec75 100644 --- a/lib/routes/asus/bios.tsx +++ b/lib/routes/asus/bios.tsx @@ -104,13 +104,15 @@ async function handler(ctx) { const language = ctx.req.param('lang') ?? 'en'; const productInfo = await getProductInfo(model, language); const biosAPI = - language === 'zh' ? `https://www.asus.com.cn/support/api/product.asmx/GetPDBIOS?website=cn&model=${model}&sitelang=cn` : `https://www.asus.com/support/api/product.asmx/GetPDBIOS?website=global&model=${model}&sitelang=en`; + language === 'zh' + ? `https://www.asus.com.cn/support/webapi/ProductV2/GetPDBIOS?website=cn&pdid=${productInfo.productID}` + : `https://www.asus.com/support/webapi/ProductV2/GetPDBIOS?website=global&pdid=${productInfo.productID}`; const response = await ofetch(biosAPI); const biosList = response.Result.Obj[0].Files; const items = biosList.map((item) => ({ - title: item.Title, + title: item.Title || `${productInfo.title} BIOS ${item.Version}`, description: renderToString( language === 'zh' ? ( <> @@ -119,7 +121,7 @@ async function handler(ctx) {

版本: {item.Version}

大小: {item.FileSize}

- 下载链接: 中国下载 | 全球下载 + 下载链接: 中国下载 | 全球下载

) : ( @@ -135,7 +137,7 @@ async function handler(ctx) { Size: {item.FileSize}

- Download: {item.DownloadUrl.Global.split('/').pop().split('?')[0]} + Download: {item.DownloadUrl.Global.split('/').pop().split('?', 1)[0]}

) diff --git a/lib/routes/atcoder/post.ts b/lib/routes/atcoder/post.ts index 55130f76876f..18e2ff937989 100644 --- a/lib/routes/atcoder/post.ts +++ b/lib/routes/atcoder/post.ts @@ -46,7 +46,7 @@ async function handler(ctx) { title: item.find('.panel-title').text(), description: item.find('.panel-body').html(), link: `${rootUrl}${item.find('.panel-title a').attr('href')}`, - pubDate: timezone(parseDate(item.find('.timeago').attr('datetime')), +9), + pubDate: timezone(parseDate(item.find('.timeago').attr('datetime')), 9), }; }); 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..dd9b81d8e8f0 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,9 +51,9 @@ 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), + 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/bad/index.ts b/lib/routes/bad/index.ts index 74fcfe60b7af..0bd8050db006 100644 --- a/lib/routes/bad/index.ts +++ b/lib/routes/bad/index.ts @@ -52,7 +52,7 @@ async function handler(ctx) { link: a.attr('href'), description: item.find('.coverdiv').html(), author: item.find('.author').text().trim(), - pubDate: timezone(parseDate(item.find('time').attr('datetime')), +8), + pubDate: timezone(parseDate(item.find('time').attr('datetime')), 8), category: item .find('.label') .toArray() diff --git a/lib/routes/baidu/tieba/forum.tsx b/lib/routes/baidu/tieba/forum.tsx index 9c001b7e5f96..9433fa3e0154 100644 --- a/lib/routes/baidu/tieba/forum.tsx +++ b/lib/routes/baidu/tieba/forum.tsx @@ -72,7 +72,7 @@ async function handler(ctx) {

作者:{author_name}

), - pubDate: timezone(parseDate(time, ['HH:mm', 'M-D', 'YYYY-MM'], true), +8), + pubDate: timezone(parseDate(time, ['HH:mm', 'M-D', 'YYYY-MM'], true), 8), link: `https://tieba.baidu.com/p/${id}`, }; }); diff --git a/lib/routes/baidu/tieba/post.tsx b/lib/routes/baidu/tieba/post.tsx index cfc0a02ddb85..32eea6f955d8 100644 --- a/lib/routes/baidu/tieba/post.tsx +++ b/lib/routes/baidu/tieba/post.tsx @@ -98,7 +98,7 @@ async function handler(ctx) { {from} ), - pubDate: timezone(parseDate(time, 'YYYY-MM-DD hh:mm'), +8), + pubDate: timezone(parseDate(time, 'YYYY-MM-DD hh:mm'), 8), link: `https://tieba.baidu.com/p/${id}?pid=${content.post_id}#${content.post_id}`, }; }), diff --git a/lib/routes/baidu/tieba/search.tsx b/lib/routes/baidu/tieba/search.tsx index 537559918462..9b8e5810c374 100644 --- a/lib/routes/baidu/tieba/search.tsx +++ b/lib/routes/baidu/tieba/search.tsx @@ -88,7 +88,7 @@ async function handler(ctx) { ), author, - pubDate: timezone(parseDate(time, 'YYYY-MM-DD HH:mm'), +8), + pubDate: timezone(parseDate(time, 'YYYY-MM-DD HH:mm'), 8), link, }; }), diff --git a/lib/routes/baidu/tieba/user.ts b/lib/routes/baidu/tieba/user.ts index d757866eb549..4a806ebb54ae 100644 --- a/lib/routes/baidu/tieba/user.ts +++ b/lib/routes/baidu/tieba/user.ts @@ -45,7 +45,7 @@ async function handler(ctx) { imgurl = item.find('ul.n_media.clearfix img').attr('original'); return { title: item.find('div.thread_name a').attr('title'), - pubDate: timezone(parseDate(item.parent().find('div .n_post_time').text(), ['YYYY-MM-DD', 'HH:mm']), +8), + pubDate: timezone(parseDate(item.parent().find('div .n_post_time').text(), ['YYYY-MM-DD', 'HH:mm']), 8), description: `${item.find('div.n_txt').text()}
`, link: item.find('div.thread_name a').attr('href'), }; 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: ` item && item.link) .map((item) => { - const link = item.link.split('?')[0]; + const link = item.link.split('?', 1)[0]; return { ...item, // https://www.bbc.co.uk/zhongwen/simp/index.xml returns trad regardless of lang parameter diff --git a/lib/routes/bbc/utils.tsx b/lib/routes/bbc/utils.tsx index abfd5d3bc272..a298262cfb74 100644 --- a/lib/routes/bbc/utils.tsx +++ b/lib/routes/bbc/utils.tsx @@ -50,7 +50,8 @@ type Block = { const applyAttributes = (content: JSX.Element | string, attributes?: BlockAttribute[]): JSX.Element | string => { let result: JSX.Element | string = content; - for (const attribute of attributes ?? []) { + const attributeList = attributes ?? []; + for (const attribute of attributeList) { switch (attribute) { case 'bold': result = {result}; @@ -355,7 +356,7 @@ export const extractInitialData = ($: CheerioAPI): any => { const initialDataText = JSON.parse( $('script:contains("window.__INITIAL_DATA__")') .text() - .match(/window\.__INITIAL_DATA__\s*=\s*(.*);/)?.[1] ?? '"{}"' + .match(/window\.__INITIAL_DATA__\s*=\s*(\S.*)?;/)?.[1] ?? '"{}"' ); return JSON.parse(initialDataText); diff --git a/lib/routes/bc3ts/list.tsx b/lib/routes/bc3ts/list.tsx index cb365ff06cf3..586b83e0d46d 100644 --- a/lib/routes/bc3ts/list.tsx +++ b/lib/routes/bc3ts/list.tsx @@ -47,7 +47,7 @@ const MediaList = ({ media }: { media: Media[] }) => ( async function handler(ctx) { const { sort = '1' } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const response = await ofetch('https://app.bc3ts.net/post/list/v2', { headers: { @@ -63,7 +63,7 @@ async function handler(ctx) { }); const items = response.data.map((p) => ({ - title: p.title ?? p.content.split('\n')[0], + title: p.title ?? p.content.split('\n', 1)[0], description: p.content.replaceAll('\n', '
') + (p.media.length && renderMedia(p.media)), link: `${baseUrl}/post/${p.id}`, author: p.user.name, diff --git a/lib/routes/bdys/index.tsx b/lib/routes/bdys/index.tsx index 45c164ea937f..2e910f432ce8 100644 --- a/lib/routes/bdys/index.tsx +++ b/lib/routes/bdys/index.tsx @@ -150,7 +150,7 @@ async function handler(ctx) { }); const downloadResponse = await got({ method: 'get', - url: `${rootUrl}/downloadInfo/list?mid=${item.link.split('/')[4].split('.')[0]}`, + url: `${rootUrl}/downloadInfo/list?mid=${item.link.split('/', 5)[4].split('.', 1)[0]}`, headers, }); const content = load(detailResponse.data); @@ -197,7 +197,7 @@ async function handler(ctx) { ); - item.pubDate = timezone(parseDate(content('.bg-purple-lt').text().replace('更新时间:', '')), +8); + item.pubDate = timezone(parseDate(content('.bg-purple-lt').text().replace('更新时间:', '')), 8); item.guid = `${item.link}#${content('.card h1').text()}`; item.enclosure_url = torrents.html() ? `${rootUrl}${torrents.find('a').first().attr('href')}` : downloadResponse.data.pop().url; diff --git a/lib/routes/behance/user.tsx b/lib/routes/behance/user.tsx index 0bd133fcbb64..b936b7a69b2e 100644 --- a/lib/routes/behance/user.tsx +++ b/lib/routes/behance/user.tsx @@ -164,7 +164,7 @@ async function handler(ctx) { item.description = renderDescription(project.description, project.allModules); item.category = [...new Set([...(item.category || []), ...(project.tags?.map((tag) => tag.title.toLowerCase()) || [])])]; - item.pubDate = item.pubDate || (project.publishedOn ? parseDate(project.publishedOn, 'X') : undefined); + item.pubDate ||= project.publishedOn ? parseDate(project.publishedOn, 'X') : undefined; return item; }) diff --git a/lib/routes/beijingprice/index.ts b/lib/routes/beijingprice/index.ts index d1ade1e7abbe..0422acc01968 100644 --- a/lib/routes/beijingprice/index.ts +++ b/lib/routes/beijingprice/index.ts @@ -7,7 +7,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx) => { const { category = 'jgzx/xwzx' } = 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 = 'https://www.beijingprice.cn'; const currentUrl = new URL(category.endsWith('/') ? category : `${category}/`, rootUrl).href; diff --git a/lib/routes/bendibao/news.ts b/lib/routes/bendibao/news.ts index 4e863dc8c4aa..aa0f41606f2e 100644 --- a/lib/routes/bendibao/news.ts +++ b/lib/routes/bendibao/news.ts @@ -128,7 +128,7 @@ async function handler(ctx) { .text() .replace(/发布时间:/, '') ?? content('span.public_time').text() ), - +8 + 8 ); return item; diff --git a/lib/routes/bestblogs/newsletter.ts b/lib/routes/bestblogs/newsletter.ts index 28a7a59095c8..4ce19b5920c2 100644 --- a/lib/routes/bestblogs/newsletter.ts +++ b/lib/routes/bestblogs/newsletter.ts @@ -21,7 +21,7 @@ export const route: Route = { }; async function handler(ctx) { - const pageSize = Number.parseInt(ctx.req.query('limit') ?? '10', 10); + const pageSize = Number(ctx.req.query('limit') ?? '10'); const response = await ofetch('https://www.bestblogs.dev/api/proxy/newsletters', { method: 'GET', diff --git a/lib/routes/bgmlist/onair.tsx b/lib/routes/bgmlist/onair.tsx index f61d9bcd56f7..7f1c2a48e34e 100644 --- a/lib/routes/bgmlist/onair.tsx +++ b/lib/routes/bgmlist/onair.tsx @@ -34,7 +34,7 @@ async function handler(ctx) { item.sites.push({ site: 'dmhy', id: item.titleTranslate['zh-Hans']?.[0] ?? item.title }); const mappedSites = item.sites.map((site) => ({ title: sites[site.site].title, - url: sites[site.site].urlTemplate.replaceAll('{{id}}', site.id), + url: sites[site.site].urlTemplate.replaceAll('{{id}}', () => site.id), begin: site.begin, })); return { diff --git a/lib/routes/bilibili/cache.ts b/lib/routes/bilibili/cache.ts index 213727ec6635..0aeaa6e92b04 100644 --- a/lib/routes/bilibili/cache.ts +++ b/lib/routes/bilibili/cache.ts @@ -21,18 +21,20 @@ const subtitleLimiterQueue = new RateLimiterQueue(subtitleLimiter, { }); const getConfiguredCookie = () => { - if (Object.keys(config.bilibili.cookies).length > 0) { - // Update b_lsid in cookies - for (const key of Object.keys(config.bilibili.cookies)) { - const cookie = config.bilibili.cookies[key]; - if (cookie) { - const updatedCookie = cookie.replace(/b_lsid=[0-9A-F]+_[0-9A-F]+/, `b_lsid=${utils.lsid()}`); - config.bilibili.cookies[key] = updatedCookie; - } - } + if (Object.keys(config.bilibili.cookies).length === 0) { + return; + } - return config.bilibili.cookies[Object.keys(config.bilibili.cookies)[Math.floor(Math.random() * Object.keys(config.bilibili.cookies).length)]] || ''; + // Update b_lsid in cookies + for (const key of Object.keys(config.bilibili.cookies)) { + const cookie = config.bilibili.cookies[key]; + if (cookie) { + const updatedCookie = cookie.replace(/b_lsid=[0-9A-F]+_[0-9A-F]+/, () => `b_lsid=${utils.lsid()}`); + config.bilibili.cookies[key] = updatedCookie; + } } + + return config.bilibili.cookies[Object.keys(config.bilibili.cookies)[Math.floor(Math.random() * Object.keys(config.bilibili.cookies).length)]] || ''; }; const getCookie = (disableConfig = false) => { @@ -50,12 +52,13 @@ const getCookie = (disableConfig = false) => { onBeforeLoad: (page) => { waitForRequest = new Promise((resolve) => { page.on('requestfinished', async (request) => { - if (request.url() === 'https://api.bilibili.com/x/web-interface/nav') { - const cookies = await page.cookies(); - let cookieString = cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; '); - cookieString = cookieString.replace(/b_lsid=[0-9A-F]+_[0-9A-F]+/, `b_lsid=${utils.lsid()}`); - resolve(cookieString); + if (request.url() !== 'https://api.bilibili.com/x/web-interface/nav') { + return; } + const cookies = await page.context().cookies(); + let cookieString = cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; '); + cookieString = cookieString.replace(/b_lsid=[0-9A-F]+_[0-9A-F]+/, () => `b_lsid=${utils.lsid()}`); + resolve(cookieString); }); }); }, @@ -99,7 +102,7 @@ const getWbiVerifyString = () => { }); const imgUrl = navResponse.data.wbi_img.img_url; const subUrl = navResponse.data.wbi_img.sub_url; - const r = imgUrl.slice(imgUrl.lastIndexOf('/') + 1).split('.')[0] + subUrl.slice(subUrl.lastIndexOf('/') + 1).split('.')[0]; + const r = imgUrl.slice(imgUrl.lastIndexOf('/') + 1).split('.', 1)[0] + subUrl.slice(subUrl.lastIndexOf('/') + 1).split('.', 1)[0]; // const { body: spaceResponse } = await got('https://space.bilibili.com/1', { // headers: { // Referer: 'https://www.bilibili.com/', @@ -117,10 +120,12 @@ const getWbiVerifyString = () => { // 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, // 62, 11, 36, 20, 34, 44, 52, // ]; - const array = JSON.parse(jsResponse.match(/\[(?:\d+,){63}\d+]/)); + const array = JSON.parse(jsResponse.match(/\[(?:\d+,){63}\d+\]/)); const o = []; for (const t of array) { - r.charAt(t) && o.push(r.charAt(t)); + if (r.charAt(t)) { + o.push(r.charAt(t)); + } } return o.join('').slice(0, 32); }); @@ -350,7 +355,7 @@ const getArticleDataFromCvid = async (cvid, uid) => { const newFormatData = JSON.parse( $('script:contains("window.__INITIAL_STATE__")') .text() - .match(/window\.__INITIAL_STATE__\s*=\s*(.*?);\(/)[1] + .match(/window\.__INITIAL_STATE__\s*=\s*(\S.*?)?;\(/)[1] ); if (newFormatData?.readInfo?.opus?.content?.paragraphs) { @@ -362,13 +367,11 @@ const getArticleDataFromCvid = async (cvid, uid) => { description += `

${text.word.words}

`; } } - } - if (element.para_type === 2) { + } else if (element.para_type === 2) { for (const image of element.pic.pics) { description += `

`; } - } - if (element.para_type === 3 && element.line?.pic?.url) { + } else if (element.para_type === 3 && element.line?.pic?.url) { description += `
`; } } diff --git a/lib/routes/bilibili/danmaku.ts b/lib/routes/bilibili/danmaku.ts index a29a8df35585..0f429e617725 100644 --- a/lib/routes/bilibili/danmaku.ts +++ b/lib/routes/bilibili/danmaku.ts @@ -72,9 +72,9 @@ async function handler(ctx) { link, description: `${videoName} 的 弹幕动态`, item: danmakuList.map((item) => ({ - title: `[${processFloatTime(item.p.split(',')[0])}] ${item.text}`, - pubDate: new Date(item.p.split(',')[4] * 1000).toUTCString(), - guid: `${cid}-${item.p.split(',')[4]}-${item.p.split(',')[7]}`, + title: `[${processFloatTime(item.p.split(',', 1)[0])}] ${item.text}`, + pubDate: new Date(item.p.split(',', 5)[4] * 1000).toUTCString(), + guid: `${cid}-${item.p.split(',', 5)[4]}-${item.p.split(',', 8)[7]}`, link, })), }; diff --git a/lib/routes/bilibili/dynamic.ts b/lib/routes/bilibili/dynamic.ts index 3510eaf135bb..9d236d551d1a 100644 --- a/lib/routes/bilibili/dynamic.ts +++ b/lib/routes/bilibili/dynamic.ts @@ -346,14 +346,14 @@ async function handler(ctx) { const emoji = node.emoji; description = description.replaceAll( emoji.text, - `${emoji.text}` + () => + `${emoji.text}` ); } // 处理转发带图评论的情况 if (node?.pics?.length) { const { pics, text } = node; - description = description.replaceAll( - text, + description = description.replaceAll(text, () => pics .map( (pic) => diff --git a/lib/routes/bilibili/fav.ts b/lib/routes/bilibili/fav.ts index f1d104548022..1cf631e7be6a 100644 --- a/lib/routes/bilibili/fav.ts +++ b/lib/routes/bilibili/fav.ts @@ -40,13 +40,13 @@ async function handler(ctx) { throw new Error(message ?? code); } - const userName = data.info.upper.name; + const username = data.info.upper.name; const favName = data.info.title; return { - title: `${userName} 的 bilibili 收藏夹 ${favName}`, + title: `${username} 的 bilibili 收藏夹 ${favName}`, link: `https://space.bilibili.com/${uid}/#/favlist?fid=${fid}`, - description: `${userName} 的 bilibili 收藏夹 ${favName}`, + description: `${username} 的 bilibili 收藏夹 ${favName}`, item: data.medias && diff --git a/lib/routes/bilibili/followings-dynamic.ts b/lib/routes/bilibili/followings-dynamic.ts index 45d600558a6f..566e5da1aed2 100644 --- a/lib/routes/bilibili/followings-dynamic.ts +++ b/lib/routes/bilibili/followings-dynamic.ts @@ -179,7 +179,7 @@ async function handler(ctx) { for (const item of emoji) { data_content = data_content.replaceAll( new RegExp(`\\${item.text}`, 'g'), - `${item.text}` + () => `${item.text}` ); } } diff --git a/lib/routes/bilibili/live-area.ts b/lib/routes/bilibili/live-area.ts index bce9e945e643..dbcfaeb04168 100644 --- a/lib/routes/bilibili/live-area.ts +++ b/lib/routes/bilibili/live-area.ts @@ -47,20 +47,21 @@ async function handler(ctx) { }); let parentTitle = ''; - let parentID: string; let areaTitle = ''; let areaLink = ''; for (const parentArea of nameResponse.data.data) { for (const area of parentArea.list) { - if (area.id === areaID) { - parentTitle = parentArea.name; - parentID = parentArea.id; - areaTitle = area.name; - // cateID = area.cate_id; - areaLink = `https://live.bilibili.com/p/eden/area-tags?parentAreaId=${parentID}&areaId=${areaID}`; - break; + if (area.id !== areaID) { + continue; } + + parentTitle = parentArea.name; + const parentID: string = parentArea.id; + areaTitle = area.name; + // cateID = area.cate_id; + areaLink = `https://live.bilibili.com/p/eden/area-tags?parentAreaId=${parentID}&areaId=${areaID}`; + break; } } diff --git a/lib/routes/bilibili/live-room.ts b/lib/routes/bilibili/live-room.ts index 800b4c83f63a..49f25b6a876f 100644 --- a/lib/routes/bilibili/live-room.ts +++ b/lib/routes/bilibili/live-room.ts @@ -34,7 +34,7 @@ async function handler(ctx) { let roomID = ctx.req.param('roomID'); // 短号查询长号 - if (Number.parseInt(roomID, 10) < 10000) { + if (Number(roomID) < 10000) { roomID = await cache.getLiveIDFromShortID(roomID); } const info = await cache.getUserInfoFromLiveID(roomID); diff --git a/lib/routes/bilibili/partion-ranking.ts b/lib/routes/bilibili/partion-ranking.ts index 3401482443f2..c827368a22d8 100644 --- a/lib/routes/bilibili/partion-ranking.ts +++ b/lib/routes/bilibili/partion-ranking.ts @@ -13,7 +13,7 @@ function formatDate(now) { const year = now.getFullYear(); const month = now.getMonth() + 1; const date = now.getDate(); - const dateTime = year + '' + (month >= 10 ? month : '0' + month) + '' + (date >= 10 ? date : '0' + date); + const dateTime = year + '' + (month >= 10 ? month : '0' + month) + (date >= 10 ? date : '0' + date); return dateTime; } diff --git a/lib/routes/bilibili/user-channel.ts b/lib/routes/bilibili/user-channel.ts index 4e8ba7ace767..b4e17b8b0e5c 100644 --- a/lib/routes/bilibili/user-channel.ts +++ b/lib/routes/bilibili/user-channel.ts @@ -51,7 +51,7 @@ async function handler(ctx) { if (!channelInfo) { return notFoundData; } - const [userName, face] = await cacheIn.getUsernameAndFaceFromUID(uid); + const [username, face] = await cacheIn.getUsernameAndFaceFromUID(uid); const host = `https://api.bilibili.com/x/series/archives?mid=${uid}&series_id=${sid}&only_normal=true&sort=desc&pn=1&ps=${limit}`; const response = await got(host, { @@ -66,9 +66,9 @@ async function handler(ctx) { } return { - title: `${userName} 的 bilibili 频道 ${channelInfo.meta.name}`, + title: `${username} 的 bilibili 频道 ${channelInfo.meta.name}`, link, - description: `${userName} 的 bilibili 频道`, + description: `${username} 的 bilibili 频道`, image: face, logo: face, icon: face, @@ -77,7 +77,7 @@ async function handler(ctx) { description: utils.renderUGCDescription(embed, item.pic, '', item.aid, undefined, item.bvid), pubDate: parseDate(item.pubdate, 'X'), link: item.pubdate > utils.bvidTime && item.bvid ? `https://www.bilibili.com/video/${item.bvid}` : `https://www.bilibili.com/video/av${item.aid}`, - author: userName, + author: username, })), }; } diff --git a/lib/routes/bilibili/user-collection.ts b/lib/routes/bilibili/user-collection.ts index fa09627dc240..29b3110189b5 100644 --- a/lib/routes/bilibili/user-collection.ts +++ b/lib/routes/bilibili/user-collection.ts @@ -43,7 +43,7 @@ async function handler(ctx) { const limit = ctx.req.query('limit') ?? 25; const link = `https://space.bilibili.com/${uid}/channel/collectiondetail?sid=${sid}`; - const [userName, face] = await cache.getUsernameAndFaceFromUID(uid); + const [username, face] = await cache.getUsernameAndFaceFromUID(uid); const host = `https://api.bilibili.com/x/polymer/web-space/seasons_archives_list?mid=${uid}&season_id=${sid}&sort_reverse=${sortReverse}&page_num=${page}&page_size=${limit}`; const response = await got(host, { @@ -58,9 +58,9 @@ async function handler(ctx) { } return { - title: `${userName} 的 bilibili 合集 ${data.meta.name}`, + title: `${username} 的 bilibili 合集 ${data.meta.name}`, link, - description: `${userName} 的 bilibili 合集`, + description: `${username} 的 bilibili 合集`, image: face, logo: face, icon: face, @@ -69,7 +69,7 @@ async function handler(ctx) { description: utils.renderUGCDescription(embed, item.pic, '', item.aid, undefined, item.bvid), pubDate: parseDate(item.pubdate, 'X'), link: item.pubdate > utils.bvidTime && item.bvid ? `https://www.bilibili.com/video/${item.bvid}` : `https://www.bilibili.com/video/av${item.aid}`, - author: userName, + author: username, })), }; } diff --git a/lib/routes/bilibili/video.ts b/lib/routes/bilibili/video.ts index 9b2a66bdfae5..c34a821c3b29 100644 --- a/lib/routes/bilibili/video.ts +++ b/lib/routes/bilibili/video.ts @@ -140,7 +140,7 @@ async function applyCookie(page: Page, cookie: string) { .filter((item) => item !== undefined); if (cookies.length > 0) { - await page.setCookie(...cookies); + await page.context().addCookies(cookies); } } @@ -184,9 +184,9 @@ async function fetchVideoListFromBrowser(uid: string): Promise { await applyCookie(page, cookie); } - await page.setRequestInterception(true); - page.on('request', (request) => { - allowedBrowserRequestTypes.has(request.resourceType()) ? request.continue() : request.abort(); + await page.route('**/*', (route) => { + const request = route.request(); + allowedBrowserRequestTypes.has(request.resourceType()) ? route.continue() : route.abort(); }); }, gotoConfig: { waitUntil: 'domcontentloaded' }, diff --git a/lib/routes/bilibili/wasm-exec.ts b/lib/routes/bilibili/wasm-exec.ts index b4359a3da0fc..6159de0ac578 100644 --- a/lib/routes/bilibili/wasm-exec.ts +++ b/lib/routes/bilibili/wasm-exec.ts @@ -1,8 +1,17 @@ // oxlint-disable unicorn/prefer-math-trunc +// oxlint-disable unicorn-js/no-this-outside-of-class +// oxlint-disable unicorn-js/no-array-from-fill +// oxlint-disable unicorn-js/no-global-object-property-assignment +// oxlint-disable unicorn-js/no-unnecessary-global-this +// oxlint-disable unicorn-js/no-undeclared-class-members +// oxlint-disable unicorn-js/prefer-array-from-map +// oxlint-disable unicorn-js/require-array-sort-compare +// oxlint-disable unicorn-js/prefer-short-arrow-method +// oxlint-disable unicorn-js/prefer-block-statement-over-iife // oxlint-disable no-unused-vars +// oxlint-disable unicorn/consistent-function-scoping /* eslint-disable prefer-rest-params */ /* eslint-disable default-case */ -/* eslint-disable unicorn/consistent-function-scoping */ /* eslint-disable no-console */ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -165,7 +174,7 @@ throw new Error('globalThis.TextDecoder is not available, polyfill required'); } - const encoder = new TextEncoder('utf-8'); + const encoder = new TextEncoder(); const decoder = new TextDecoder('utf-8'); globalThis.Go = class { @@ -634,7 +643,7 @@ _makeFuncWrapper(id) { // somehow avoiding aliasing this with an arrow function doesn't work - // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias + // oxlint-disable-next-line unicorn/no-this-assignment typescript/no-this-alias const go = this; return function () { const event = { id, this: this, args: arguments }; diff --git a/lib/routes/binance/announcement.ts b/lib/routes/binance/announcement.ts index 3600d9d27cde..ec1a407be537 100644 --- a/lib/routes/binance/announcement.ts +++ b/lib/routes/binance/announcement.ts @@ -50,7 +50,7 @@ const handler: Route['handler'] = async (ctx) => { const baseUrl = 'https://www.binance.com'; const rawType = ctx.req.param('type'); const rawLang = ctx.req.param('lang'); - const limit = Number.parseInt(ctx.req.query('limit') ?? '20', 10); + const limit = Number(ctx.req.query('limit') ?? '20'); const pageSize = Number.isNaN(limit) || limit <= 0 ? 20 : limit; let type = rawType; @@ -90,7 +90,7 @@ const handler: Route['handler'] = async (ctx) => { lang: language, }; - const response = (await ofetch(listUrl.toString(), { headers })) as ArticleListResponse; + const response = (await ofetch(listUrl.href, { headers })) as ArticleListResponse; const catalogs = response.data?.catalogs ?? []; const itemsWithDate = catalogs.flatMap((catalog) => diff --git a/lib/routes/bing/search.ts b/lib/routes/bing/search.ts index 85f352e415f1..5faef975aaeb 100644 --- a/lib/routes/bing/search.ts +++ b/lib/routes/bing/search.ts @@ -42,7 +42,7 @@ async function handler(ctx) { }); const url = new URL('https://cn.bing.com/search'); url.search = searchParams.toString(); - const data = await parser.parseURL(url.toString()); + const data = await parser.parseURL(url.href); return { title: data.title, link: data.link, diff --git a/lib/routes/bioone/featured.ts b/lib/routes/bioone/featured.ts index a4bc770e64b9..8afafa0b09d2 100644 --- a/lib/routes/bioone/featured.ts +++ b/lib/routes/bioone/featured.ts @@ -40,7 +40,7 @@ async function handler(ctx) { .toArray() .map((item) => { item = $(item); - const link = item.attr('href').split('?')[0]; + const link = item.attr('href').split('?', 1)[0]; return { title: item.text(), diff --git a/lib/routes/biquge/index.ts b/lib/routes/biquge/index.ts index 4da457d8e164..d2d27c128035 100644 --- a/lib/routes/biquge/index.ts +++ b/lib/routes/biquge/index.ts @@ -51,7 +51,7 @@ async function handler(ctx) { const $ = load(iconv.decode(response.data, encoding)); const author = $('meta[property="og:novel:author"]').attr('content'); - const pubDate = timezone(parseDate($('meta[property="og:novel:update_time"]').attr('content')), +8); + const pubDate = timezone(parseDate($('meta[property="og:novel:update_time"]').attr('content')), 8); let items = $('dl dd a') .toArray() diff --git a/lib/routes/bitget/announcement.ts b/lib/routes/bitget/announcement.ts index 3ac9b20874d3..e9d070c61d13 100644 --- a/lib/routes/bitget/announcement.ts +++ b/lib/routes/bitget/announcement.ts @@ -111,7 +111,7 @@ const handler: Route['handler'] = async (ctx) => { const nextData = JSON.parse($('script#__NEXT_DATA__').text()); dataItem.description = nextData.props.pageProps.details?.content || nextData.props.pageProps.pageInitInfo?.ruleContent || item.content || ''; } catch (error: any) { - if (error.name && (error.name === 'HTTPError' || error.name === 'RequestError' || error.name === 'FetchError')) { + if (error.name && ['HTTPError', 'RequestError', 'FetchError'].includes(error.name)) { dataItem.description = item.content ?? ''; } else { throw error; diff --git a/lib/routes/bjfu/it/utils.ts b/lib/routes/bjfu/it/utils.ts index ef3517502e25..2998f726e3d8 100644 --- a/lib/routes/bjfu/it/utils.ts +++ b/lib/routes/bjfu/it/utils.ts @@ -50,7 +50,7 @@ const ProcessFeed = (base, list, caches) => .text() .match(/\d{4}-\d{2}-\d{2}/) ), - +8 + 8 ); // 使用tryGet方法从缓存获取内容。 diff --git a/lib/routes/bjfu/jwc/utils.ts b/lib/routes/bjfu/jwc/utils.ts index c0e633ebae64..526bbda34d6a 100644 --- a/lib/routes/bjfu/jwc/utils.ts +++ b/lib/routes/bjfu/jwc/utils.ts @@ -37,7 +37,7 @@ const ProcessFeed = (base, list, caches) => .text() .match(/\d{4}-\d{2}-\d{2}/) ), - +8 + 8 ); // 使用tryGet方法从缓存获取内容。 diff --git a/lib/routes/bjfu/news/utils.ts b/lib/routes/bjfu/news/utils.ts index 3511db1b2e41..b13dcc30b377 100644 --- a/lib/routes/bjfu/news/utils.ts +++ b/lib/routes/bjfu/news/utils.ts @@ -20,7 +20,7 @@ async function loadContent(link) { .text() .match(/\d{4}(?:\/\d{2}){2}/) ), - +8 + 8 ); // 提取内容 diff --git a/lib/routes/bjnews/column.ts b/lib/routes/bjnews/column.ts index 6529d9b3e344..82723a8e5352 100644 --- a/lib/routes/bjnews/column.ts +++ b/lib/routes/bjnews/column.ts @@ -29,8 +29,8 @@ async function handler(ctx) { const list = res.data.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 15).map((e) => ({ title: e.row.title, guid: e.uuid, - pubDate: timezone(parseDate(e.row.publish_time), +8), - updated: timezone(parseDate(e.row.update_time), +8), + pubDate: timezone(parseDate(e.row.publish_time), 8), + updated: timezone(parseDate(e.row.update_time), 8), link: `https://www.bjnews.com.cn/detail/${e.uuid}.html`, })); diff --git a/lib/routes/bjnews/utils.ts b/lib/routes/bjnews/utils.ts index 86379eae19ea..1df79a8a2c0e 100644 --- a/lib/routes/bjnews/utils.ts +++ b/lib/routes/bjnews/utils.ts @@ -11,7 +11,7 @@ export function fetchArticle(item) { const $d = load(responses); // $d('img').each((i, e) => $(e).attr('referrerpolicy', 'no-referrer')); - item.pubDate = timezone(parseDate($d('.left-info .timer').text()), +8); + item.pubDate = timezone(parseDate($d('.left-info .timer').text()), 8); item.author = $d('.left-info .reporter').text(); item.description = $d('#contentStr').html(); diff --git a/lib/routes/bjp/apod.ts b/lib/routes/bjp/apod.ts index 5e17a040401e..8fcc8bed6fc6 100644 --- a/lib/routes/bjp/apod.ts +++ b/lib/routes/bjp/apod.ts @@ -51,7 +51,7 @@ async function handler(ctx) { }; }) .toSorted((a, b) => b.pubDate - a.pubDate) - .slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10); + .slice(0, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 10); const items = await Promise.all( list.map((e) => diff --git a/lib/routes/bjsk/index.ts b/lib/routes/bjsk/index.ts index 11bf8d02db69..e5deceeea0ae 100644 --- a/lib/routes/bjsk/index.ts +++ b/lib/routes/bjsk/index.ts @@ -55,7 +55,8 @@ async function handler(ctx) { item.description = $('.article-main').html(); item.author = $('.info') .text() - .match(/作者:(.*)\s+来源/)[1]; + .match(/作者:(.*?)来源/)[1] + .trim(); return item; }) ) diff --git a/lib/routes/bjtu/gs.ts b/lib/routes/bjtu/gs.ts index dec0963cf217..4711d23ba439 100644 --- a/lib/routes/bjtu/gs.ts +++ b/lib/routes/bjtu/gs.ts @@ -130,7 +130,7 @@ const getItem = (item, selector) => { const newsDate = item .find('span') .text() - .match(/\d{4}(-|\/|.)\d{1,2}\1\d{1,2}/)[0]; + .match(/\d{4}(.)\d{1,2}\1\d{1,2}/)[0]; const infoTitle = newsInfo.text(); const link = rootURL + newsInfo.attr('href'); diff --git a/lib/routes/bjwxdxh/index.ts b/lib/routes/bjwxdxh/index.ts index b90d7a8aff02..baec7979c1b0 100644 --- a/lib/routes/bjwxdxh/index.ts +++ b/lib/routes/bjwxdxh/index.ts @@ -59,9 +59,9 @@ async function handler(ctx) { const content = load(response.data); const info = content('div.info') .text() - .match(/作者:(.*?)\s+发布于:(.*?\s+.*?)\s/); + .match(/作者:(\S*)\s+发布于:(\S*\s+.*?)\s/); item.author = info[1]; - item.pubDate = timezone(parseDate(info[2], 'YYYY-MM-DD HH:mm:ss'), +8); + item.pubDate = timezone(parseDate(info[2], 'YYYY-MM-DD HH:mm:ss'), 8); item.description = content('div#con').html().trim().replaceAll('\n', ''); return item; }) diff --git a/lib/routes/bjx/huanbao.ts b/lib/routes/bjx/huanbao.ts index a2e2fc1044fa..33ff748fcf7e 100644 --- a/lib/routes/bjx/huanbao.ts +++ b/lib/routes/bjx/huanbao.ts @@ -88,8 +88,8 @@ const fetchPage = (link) => const item = { title: $page('title').text(), - description: pages.reduce((desc, $p) => desc + $p('.cc-article').html(), ''), - pubDate: timezone(parseDate($page('.cc-headline .box p span').eq(0).text()), +8), + description: pages.map(($p) => $p('.cc-article').html() ?? '').join(''), + pubDate: timezone(parseDate($page('.cc-headline .box p span').eq(0).text()), 8), link, author: $page('.cc-headline .box p span').eq(1).text(), }; diff --git a/lib/routes/blizzard/news-cn.ts b/lib/routes/blizzard/news-cn.ts index 34fcea7baa23..e0d38c71c359 100644 --- a/lib/routes/blizzard/news-cn.ts +++ b/lib/routes/blizzard/news-cn.ts @@ -97,7 +97,7 @@ const detailParsers = { }; function getList(category, $) { - return parsers[category] ? parsers[category]($) : []; + return Object.hasOwn(parsers, category) ? parsers[category]($) : []; } async function fetchDetail(item, category) { @@ -113,7 +113,7 @@ async function fetchDetail(item, category) { async function handler(ctx) { const category = ctx.req.param('category') || 'ow'; - if (!categoryNames[category]) { + if (!Object.hasOwn(categoryNames, category)) { throw new Error('Invalid category'); } diff --git a/lib/routes/blizzard/news.ts b/lib/routes/blizzard/news.ts index 9eae841dbfd8..5beb0cd4ba82 100644 --- a/lib/routes/blizzard/news.ts +++ b/lib/routes/blizzard/news.ts @@ -156,10 +156,9 @@ async function handler(ctx) { let rssTitle = ''; const { - data: { - feed: { contentItems: response }, - }, + data: { feed }, } = await got(`${apiUrl}?${getSearchParams(category)}`); + const { contentItems: response } = feed; const list = response.map((item) => { const content = item.properties; diff --git a/lib/routes/blockworks/index.ts b/lib/routes/blockworks/index.ts index e1a5ad6b77c4..e09cac311504 100644 --- a/lib/routes/blockworks/index.ts +++ b/lib/routes/blockworks/index.ts @@ -36,7 +36,7 @@ export const route: Route = { async function handler(ctx): Promise { const rssUrl = 'https://blockworks.co/feed'; const feed = await parser.parseURL(rssUrl); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; // Limit to 20 items const limitedItems = feed.items.slice(0, limit); @@ -46,7 +46,7 @@ async function handler(ctx): Promise { limitedItems .map((item) => ({ ...item, - link: item.link?.split('?')[0], + link: item.link?.split('?', 1)[0], })) .map((item) => cache.tryGet(item.link!, async () => { diff --git a/lib/routes/blogread/index.ts b/lib/routes/blogread/index.ts index c6091c6c17ff..3827985a28dd 100644 --- a/lib/routes/blogread/index.ts +++ b/lib/routes/blogread/index.ts @@ -34,7 +34,7 @@ async function handler() { description: elem.find('dd').eq(0).text(), link: $link.attr('href'), author: elem.find('.small a').eq(0).text(), - pubDate: elem.find('dd').eq(1).text().split('\n')[2], + pubDate: elem.find('dd').eq(1).text().split('\n', 3)[2], }; }); return { diff --git a/lib/routes/bloomberg/index.ts b/lib/routes/bloomberg/index.ts index cb81d7c7924f..b54ea9296718 100644 --- a/lib/routes/bloomberg/index.ts +++ b/lib/routes/bloomberg/index.ts @@ -28,7 +28,7 @@ export const route: Route = { parameters: { site: { description: 'Site ID, can be found below', - options: Object.keys(siteTitleMapping).map((key) => ({ value: key, label: siteTitleMapping[key] })), + options: Object.entries(siteTitleMapping).map(([key, value]) => ({ value: key, label: value })), }, }, features: { diff --git a/lib/routes/bloomberg/utils.ts b/lib/routes/bloomberg/utils.ts index 215c3b716a57..3237d0bf604a 100644 --- a/lib/routes/bloomberg/utils.ts +++ b/lib/routes/bloomberg/utils.ts @@ -55,7 +55,7 @@ const apiEndpoints = { }, }; -const pageTypeRegex1 = /\/(?[\w-]*?)\/(?\d{4}-\d{2}-\d{2}\/.*)/; +const pageTypeRegex1 = /\/(?[\w-]*)\/(?\d{4}-\d{2}-\d{2}\/.*)/; const pageTypeRegex2 = /(?features\/|graphics\/)(?.*)/; const regex = [pageTypeRegex1, pageTypeRegex2]; @@ -101,7 +101,7 @@ const parseArticle = (item) => .map((a) => a && a.groups)[0]; if (group) { const { page, link } = group; - if (apiEndpoints[page]) { + if (Object.hasOwn(apiEndpoints, page)) { const api = { ...apiEndpoints[page] }; let res; @@ -110,7 +110,7 @@ const parseArticle = (item) => res = await redirectGot(apiUrl); } catch (error) { // fallback - if (error.name && (error.name === 'HTTPError' || error.name === 'RequestError' || error.name === 'FetchError')) { + if (error.name && ['HTTPError', 'RequestError', 'FetchError'].includes(error.name)) { try { res = await redirectGot(item.link); } catch { @@ -224,7 +224,7 @@ const parseReactRendererPage = async (res, api, item) => { return await parseStoryJson(res._data, item); } catch (error) { // fallback - if (error.name && (error.name === 'HTTPError' || error.name === 'RequestError' || error.name === 'FetchError')) { + if (error.name && ['HTTPError', 'RequestError', 'FetchError'].includes(error.name)) { return { title: item.title, link: item.link, @@ -271,7 +271,8 @@ const processLedeMedia = async (story_json) => { video: kind === 'video' && (await processVideo(story_json.ledeAttachment.bmmrId)), }; return renderLedeMedia(media); - } else if (story_json.lede) { + } + if (story_json.lede) { const lede = story_json.lede; const image = { src: lede.url, @@ -280,7 +281,8 @@ const processLedeMedia = async (story_json) => { credit: lede.credit?.replaceAll(capRegex, '') ?? '', }; return renderImageFigure(image); - } else if (story_json.imageAttachments) { + } + if (story_json.imageAttachments) { const attachment = Object.values(story_json.imageAttachments)[0]; if (attachment) { const image = { @@ -292,7 +294,8 @@ const processLedeMedia = async (story_json) => { return renderImageFigure(image); } return ''; - } else if (story_json.type === 'Lede') { + } + if (story_json.type === 'Lede') { const props = story_json.props; const media = { @@ -372,7 +375,7 @@ const processBody = async (body_html, story_json) => { return $.html(); }; -const processVideo = async (bmmrId, summary) => { +const processVideo = async (bmmrId, summary?) => { const api = `https://www.bloomberg.com/multimedia/api/embed?id=${bmmrId}`; const res = await redirectGot(api); @@ -407,15 +410,16 @@ const nodeRenderers = { paragraph: async (node, nextNode) => `

${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) => `
${await nextNode(node.content)}
`, 'inline-recirc': async (node, nextNode) => `
${await nextNode(node.content)}
`, @@ -578,7 +582,7 @@ const nextNode = async (nodes) => { }; const nodeToHtmlString = async (node, obj) => { - if (!node.type || !nodeRenderers[node.type]) { + if (!node.type || !Object.hasOwn(nodeRenderers, node.type)) { return `${node.type}`; } const str = await nodeRenderers[node.type](node, nextNode, obj); diff --git a/lib/routes/bluestacks/release.ts b/lib/routes/bluestacks/release.ts index bf3c823ecf1e..e249a296c352 100644 --- a/lib/routes/bluestacks/release.ts +++ b/lib/routes/bluestacks/release.ts @@ -32,11 +32,11 @@ export const route: Route = { }; async function handler() { - const browser = await playwright(); - const page = await browser.newPage(); - await page.setRequestInterception(true); - page.on('request', (request) => { - request.resourceType() === 'document' || request.resourceType() === 'script' ? 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' ? route.continue() : route.abort(); }); await page.goto(pageUrl, { waitUntil: 'domcontentloaded', @@ -46,7 +46,7 @@ async function handler() { const $ = cheerio.load(res); - const items = $('div h3 a') + const list = $('div h3 a') .toArray() .map((item) => { item = $(item); @@ -56,13 +56,13 @@ async function handler() { }; }); - await Promise.all( - items.map((item) => + const items = await Promise.all( + list.map((item) => cache.tryGet(item.link, 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(item.link, { waitUntil: 'domcontentloaded', @@ -79,7 +79,7 @@ async function handler() { ) ); - await browser.close(); + await context.close(); return { title: $('.article__title').text().trim(), diff --git a/lib/routes/bmkg/earthquake.ts b/lib/routes/bmkg/earthquake.ts index d60f1dd89ac7..18eb1bb9f635 100644 --- a/lib/routes/bmkg/earthquake.ts +++ b/lib/routes/bmkg/earthquake.ts @@ -41,7 +41,7 @@ async function handler() { return { title: `${td[2].children[0].data}|${td[3].children[0].data}|${td[4].children[0].data}|${td[5].children[0].data}|${td[6].children[0].data}`, link: url, - pubDate: timezone(parseDate(`${td[1].children[0].data} ${td[1].children[2].data.slice(0, 8)}`, 'DD-MM-YY HH:mm:ss'), +7), + pubDate: timezone(parseDate(`${td[1].children[0].data} ${td[1].children[2].data.slice(0, 8)}`, 'DD-MM-YY HH:mm:ss'), 7), }; }); diff --git a/lib/routes/bnext/index.ts b/lib/routes/bnext/index.ts index 2c9848c166cf..e15734e4a6e0 100644 --- a/lib/routes/bnext/index.ts +++ b/lib/routes/bnext/index.ts @@ -36,7 +36,7 @@ async function handler() { const enclosure = item.enclosure; const enclosure_url = enclosure?.url; const enclosure_type = enclosure?.type; - const enclosure_length = enclosure?.length ? Number(enclosure.length) : undefined; + const enclosure_length = enclosure?.length || undefined; return { title: item.title ?? item.link ?? 'Untitled', diff --git a/lib/routes/bntnews/index.ts b/lib/routes/bntnews/index.ts index aa117467e9a4..eb0f25f7ceb1 100644 --- a/lib/routes/bntnews/index.ts +++ b/lib/routes/bntnews/index.ts @@ -59,7 +59,7 @@ async function handler(ctx) { title: article.title, link, description: article.content, - pubDate: timezone(parseDate(article.firstPublishDate), +9), + pubDate: timezone(parseDate(article.firstPublishDate), 9), author: article.reporter?.[0]?.name || '', }; }); diff --git a/lib/routes/bnu/mba.ts b/lib/routes/bnu/mba.ts index 8a2c251bfcd5..9c6ccad967f6 100644 --- a/lib/routes/bnu/mba.ts +++ b/lib/routes/bnu/mba.ts @@ -7,7 +7,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx) => { const { category = 'xwdt' } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const rootUrl = 'https://mba.bnu.edu.cn'; const currentUrl = new URL(`${category.replace(/\/$/, '')}/`, rootUrl).href; diff --git a/lib/routes/bookwalker/search.tsx b/lib/routes/bookwalker/search.tsx index 1e2ae1daec68..0ef5ff563112 100644 --- a/lib/routes/bookwalker/search.tsx +++ b/lib/routes/bookwalker/search.tsx @@ -10,7 +10,7 @@ import ofetch from '@/utils/ofetch'; export const handler = async (ctx: Context): Promise => { const { filter = 'order=sell_desc' } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '24', 10); + const limit = Number(ctx.req.query('limit') ?? '24'); const baseUrl = 'https://www.bookwalker.com.tw'; const targetUrl: string = new URL(`search?${filter}`, baseUrl).href; diff --git a/lib/routes/booru/mmda.ts b/lib/routes/booru/mmda.ts index 69cc18361fa6..779af1236384 100644 --- a/lib/routes/booru/mmda.ts +++ b/lib/routes/booru/mmda.ts @@ -95,7 +95,7 @@ async function handler(ctx) { statisticsTages.find('li, br, strong').remove(); const statisticsStr = statisticsTages.text(); - const regex = /(?[^\s:]+)\s*:\s*(?.+)/gm; + const regex = /(?[^\s:]+)\s*:\s*(?.+)/g; const result = {}; for (const match of statisticsStr.matchAll(regex)) { const { key, value } = match.groups ?? ({} as { key: string; value: string }); diff --git a/lib/routes/bossdesign/index.ts b/lib/routes/bossdesign/index.ts index c76f02563cbe..c5d51514e77c 100644 --- a/lib/routes/bossdesign/index.ts +++ b/lib/routes/bossdesign/index.ts @@ -26,7 +26,7 @@ export const route: Route = { async function handler(ctx) { const category = ctx.req.param('category'); - const limit = Number.parseInt(ctx.req.query('limit'), 10) || undefined; + const limit = Number(ctx.req.query('limit')) || undefined; const baseUrl = 'https://www.bossdesign.cn'; const currentCategory = await cache.tryGet(`bossdesign:categories:${category}`, async () => { diff --git a/lib/routes/bse/index.ts b/lib/routes/bse/index.ts index 6154cf51cdc6..06cc8f30ae64 100644 --- a/lib/routes/bse/index.ts +++ b/lib/routes/bse/index.ts @@ -183,7 +183,7 @@ async function handler(ctx) { }, }); - const data = JSON.parse(response.data.match(/null\(\[({.*})]\)/)[1]); + const data = JSON.parse(response.data.match(/null\(\[(\{.*\})\]\)/)[1]); let items: DataItem[]; @@ -194,7 +194,7 @@ async function handler(ctx) { category: item.tags, description: item.text, link: `${rootUrl}${item.htmlUrl}`, - pubDate: timezone(parseDate(item.publishDate), +8), + pubDate: timezone(parseDate(item.publishDate), 8), })); break; diff --git a/lib/routes/bsky/feeds.ts b/lib/routes/bsky/feeds.ts index f13660c5c5b4..cba53d1fcfac 100644 --- a/lib/routes/bsky/feeds.ts +++ b/lib/routes/bsky/feeds.ts @@ -1,6 +1,5 @@ import type { Route } from '@/types'; import { ViewType } from '@/types'; -import cache from '@/utils/cache'; import { parseDate } from '@/utils/parse-date'; import { renderPost } from './templates/post'; @@ -10,7 +9,7 @@ export const route: Route = { path: '/profile/:handle/feed/:space/:routeParams?', categories: ['social-media'], view: ViewType.SocialMedia, - example: '/bsky.app/profile/jaz.bsky.social/feed/cv:cat', + example: '/bsky/profile/jaz.bsky.social/feed/cv:cat', parameters: { handle: 'User handle, can be found in URL', space: 'Space ID, can be found in URL', @@ -32,13 +31,13 @@ async function handler(ctx) { const handle = ctx.req.param('handle'); const space = ctx.req.param('space'); - const DID = await resolveHandle(handle, cache.tryGet); + const DID = await resolveHandle(handle); const uri = `at://${DID}/app.bsky.feed.generator/${space}`; - const profile = await getFeedGenerator(uri, cache.tryGet); - const feeds = await getFeed(uri, cache.tryGet); + const profile = await getFeedGenerator(uri); + const feeds = await getFeed(uri); const items = feeds.feed.map(({ post }) => ({ - title: post.record.text.split('\n')[0], + title: post.record.text.split('\n', 1)[0], description: renderPost({ text: post.record.text.replaceAll('\n', '
'), embed: post.embed, @@ -46,7 +45,7 @@ async function handler(ctx) { }), author: post.author.displayName, pubDate: parseDate(post.record.createdAt), - link: `https://bsky.app/profile/${post.author.handle}/post/${post.uri.split('app.bsky.feed.post/')[1]}`, + link: `https://bsky.app/profile/${post.author.handle}/post/${post.uri.split('app.bsky.feed.post/', 2)[1]}`, upvotes: post.likeCount, comments: post.replyCount, })); diff --git a/lib/routes/bsky/posts.ts b/lib/routes/bsky/posts.ts index 27e6a602d59a..b5e40c933353 100644 --- a/lib/routes/bsky/posts.ts +++ b/lib/routes/bsky/posts.ts @@ -2,7 +2,6 @@ import querystring from 'node:querystring'; import type { Route } from '@/types'; import { ViewType } from '@/types'; -import cache from '@/utils/cache'; import { parseDate } from '@/utils/parse-date'; import { renderPost } from './templates/post'; @@ -52,12 +51,12 @@ async function handler(ctx) { const routeParams = querystring.parse(ctx.req.param('routeParams')); const filter = routeParams.filter || 'posts_and_author_threads'; - const DID = await resolveHandle(handle, cache.tryGet); - const profile = await getProfile(DID, cache.tryGet); - const authorFeed = await getAuthorFeed(DID, filter, cache.tryGet); + const DID = await resolveHandle(handle); + const profile = await getProfile(DID); + const authorFeed = await getAuthorFeed(DID, filter); const items = authorFeed.feed.map(({ post }) => ({ - title: post.record.text.split('\n')[0], + title: post.record.text.split('\n', 1)[0], description: renderPost({ text: post.record.text.replaceAll('\n', '
'), embed: post.embed, @@ -65,7 +64,7 @@ async function handler(ctx) { }), author: post.author.displayName, pubDate: parseDate(post.record.createdAt), - link: `https://bsky.app/profile/${post.author.handle}/post/${post.uri.split('app.bsky.feed.post/')[1]}`, + link: `https://bsky.app/profile/${post.author.handle}/post/${post.uri.split('app.bsky.feed.post/', 2)[1]}`, upvotes: post.likeCount, comments: post.replyCount, })); diff --git a/lib/routes/bsky/utils.ts b/lib/routes/bsky/utils.ts index f167308a2a0b..099643bb0ed6 100644 --- a/lib/routes/bsky/utils.ts +++ b/lib/routes/bsky/utils.ts @@ -1,4 +1,5 @@ import { config } from '@/config'; +import cache from '@/utils/cache'; import got from '@/utils/got'; /** @@ -6,8 +7,8 @@ import got from '@/utils/got'; */ // https://github.com/bluesky-social/atproto/blob/main/lexicons/com/atproto/identity/resolveHandle.json -const resolveHandle = (handle, tryGet) => - tryGet(`bsky:${handle}`, async () => { +const resolveHandle = (handle) => + cache.tryGet(`bsky:${handle}`, async () => { const { data } = await got('https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle', { searchParams: { handle, @@ -17,8 +18,8 @@ const resolveHandle = (handle, tryGet) => }); // https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/actor/getProfile.json -const getProfile = (did, tryGet) => - tryGet(`bsky:profile:${did}`, async () => { +const getProfile = (did) => + cache.tryGet(`bsky:profile:${did}`, async () => { const { data } = await got('https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile', { searchParams: { actor: did, @@ -28,8 +29,8 @@ const getProfile = (did, tryGet) => }); // https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/feed/getAuthorFeed.json -const getAuthorFeed = (did, filter, tryGet) => - tryGet( +const getAuthorFeed = (did, filter) => + cache.tryGet( `bsky:authorFeed:${did}:${filter}`, async () => { const { data } = await got('https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed', { @@ -46,8 +47,8 @@ const getAuthorFeed = (did, filter, tryGet) => ); // https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/feed/getFeed.json -const getFeed = (uri, tryGet) => - tryGet( +const getFeed = (uri) => + cache.tryGet( `bsky:feed:${uri}`, async () => { const { data } = await got('https://public.api.bsky.app/xrpc/app.bsky.feed.getFeed', { @@ -63,8 +64,8 @@ const getFeed = (uri, tryGet) => ); // https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/feed/getFeedGenerator.json -const getFeedGenerator = (uri, tryGet) => - tryGet( +const getFeedGenerator = (uri) => + cache.tryGet( `bsky:feedGenerator:${uri}`, async () => { const { data } = await got('https://public.api.bsky.app/xrpc/app.bsky.feed.getFeedGenerator', { diff --git a/lib/routes/bt0/util.ts b/lib/routes/bt0/util.ts index 9892befcb460..405916add713 100644 --- a/lib/routes/bt0/util.ts +++ b/lib/routes/bt0/util.ts @@ -33,7 +33,7 @@ const genSize = (sizeStr) => { return 0; } - const value = Number.parseFloat(match[1]); + const value = Number(match[1]); const unit = match[3].toUpperCase(); let bytes; diff --git a/lib/routes/btzj/index.tsx b/lib/routes/btzj/index.tsx index ef9fb00d0e0d..711c1443cc21 100644 --- a/lib/routes/btzj/index.tsx +++ b/lib/routes/btzj/index.tsx @@ -145,7 +145,7 @@ async function handler(ctx) { item.description = content('.post').html(); item.author = content('.purple, .grey').first().prev().text(); - item.pubDate = timezone(parseDate(content('.bg2 b').first().text()), +8); + item.pubDate = timezone(parseDate(content('.bg2 b').first().text()), 8); if (torrents.length > 0) { item.description += renderTorrents(torrents.toArray().map((t) => content(t).parent().html())); diff --git a/lib/routes/buaa/jiaowu.ts b/lib/routes/buaa/jiaowu.ts index 2bcfc4da5792..21fe46e885b5 100644 --- a/lib/routes/buaa/jiaowu.ts +++ b/lib/routes/buaa/jiaowu.ts @@ -99,7 +99,7 @@ async function getList(url: string | URL, form: Record = {}) { return { title: $('a').text(), link, - pubDate: timezone(parseDate($('span.Floatright').text()), +8), + pubDate: timezone(parseDate($('span.Floatright').text()), 8), }; }) .filter((item) => item !== null); diff --git a/lib/routes/buaa/lib/space/newbook.tsx b/lib/routes/buaa/lib/space/newbook.tsx index f9ffebe6f285..1e548dc7ac1a 100644 --- a/lib/routes/buaa/lib/space/newbook.tsx +++ b/lib/routes/buaa/lib/space/newbook.tsx @@ -234,7 +234,7 @@ async function getItem(item: Book): Promise { return { language: item.language === 'eng' ? 'en' : 'zh-CN', title: item.title, - pubDate: item.onSelfDate ? timezone(parseDate(item.onSelfDate), +8) : undefined, + pubDate: item.onSelfDate ? timezone(parseDate(item.onSelfDate), 8) : undefined, description: content, link, }; diff --git a/lib/routes/buaa/news/index.ts b/lib/routes/buaa/news/index.ts index ad88f8572172..c8499f371a6d 100644 --- a/lib/routes/buaa/news/index.ts +++ b/lib/routes/buaa/news/index.ts @@ -44,7 +44,7 @@ async function handler(ctx: Context): Promise { return { title: title.text(), link: new URL(title.attr('href')!, baseUrl).href, - pubDate: timezone(parseDate(item.find('h2 em').text(), '[YYYY-MM-DD]'), +8), + pubDate: timezone(parseDate(item.find('h2 em').text(), '[YYYY-MM-DD]'), 8), }; }); diff --git a/lib/routes/buaa/sme.ts b/lib/routes/buaa/sme.ts index 2e370a20ed38..7869d8b61a98 100755 --- a/lib/routes/buaa/sme.ts +++ b/lib/routes/buaa/sme.ts @@ -79,7 +79,7 @@ async function getList(url) { return { title: item.find('a').text(), link: link?.startsWith('http') ? link : `${BASE_URL}/${link}`, // 有些链接是相对路径 - pubDate: timezone(parseDate(item.find('span').text()), +8), + pubDate: timezone(parseDate(item.find('span').text()), 8), }; }); return { diff --git a/lib/routes/bugzilla/bug.ts b/lib/routes/bugzilla/bug.ts index 830944edb0ac..8ca7b125168d 100644 --- a/lib/routes/bugzilla/bug.ts +++ b/lib/routes/bugzilla/bug.ts @@ -37,7 +37,7 @@ async function handler(ctx: Context): Promise { } function markdownFrom(instances: Map, separator: string = ', '): string { - return [...instances.entries()].map(([k, v]) => `[\`${k}\`](https://${v})`).join(separator); + return [...instances].map(([k, v]) => `[\`${k}\`](https://${v})`).join(separator); } export const route: Route = { diff --git a/lib/routes/bullionvault/gold-news.ts b/lib/routes/bullionvault/gold-news.ts index 04d8393a95e8..ff2cc9d09f05 100644 --- a/lib/routes/bullionvault/gold-news.ts +++ b/lib/routes/bullionvault/gold-news.ts @@ -11,7 +11,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') ?? '30', 10); + const limit = Number(ctx.req.query('limit') ?? '30'); const baseUrl = 'https://bullionvault.com'; const targetUrl: string = new URL(`gold-news${category ? `/${category}` : ''}`, baseUrl).href; diff --git a/lib/routes/bupt/jwc.ts b/lib/routes/bupt/jwc.ts index 9439c979e6c9..4051dff75c04 100644 --- a/lib/routes/bupt/jwc.ts +++ b/lib/routes/bupt/jwc.ts @@ -116,7 +116,7 @@ async function handler(ctx: Context) { // 提取并格式化发布时间 item.description = cleanedDescription; - item.pubDate = timezone(parseDate(content('.info').text().replace('发布时间:', '').trim()), +8); + item.pubDate = timezone(parseDate(content('.info').text().replace('发布时间:', '').trim()), 8); return item; }) diff --git a/lib/routes/bupt/rczp.ts b/lib/routes/bupt/rczp.ts index acf1d795c263..b3bda326ca5c 100644 --- a/lib/routes/bupt/rczp.ts +++ b/lib/routes/bupt/rczp.ts @@ -63,7 +63,7 @@ async function handler() { const content = load(detailResponse.data); item.description = content('.v_news_content').html(); - item.pubDate = timezone(parseDate(content('.info span').first().text().replace('发布时间 : ', '')), +8); + item.pubDate = timezone(parseDate(content('.info span').first().text().replace('发布时间 : ', '')), 8); return item; }) diff --git a/lib/routes/bupt/scss.ts b/lib/routes/bupt/scss.ts index 7db560446a02..28113c9b12dc 100644 --- a/lib/routes/bupt/scss.ts +++ b/lib/routes/bupt/scss.ts @@ -84,7 +84,7 @@ async function handler() { }); item.description = newsContent.text(); - item.pubDate = timezone(parseDate(item.pubDateRaw), +8); + item.pubDate = timezone(parseDate(item.pubDateRaw), 8); return item; }) diff --git a/lib/routes/byau/xinwen/index.ts b/lib/routes/byau/xinwen/index.ts index 33a051dd52bd..e2208648cac9 100644 --- a/lib/routes/byau/xinwen/index.ts +++ b/lib/routes/byau/xinwen/index.ts @@ -48,7 +48,7 @@ async function handler(ctx) { return { title: $$('a').text(), link: itemUrl, - pubDate: timezone(parseDate($$('.news_meta').text()), +8), + pubDate: timezone(parseDate($$('.news_meta').text()), 8), }; }); diff --git a/lib/routes/c114/roll.ts b/lib/routes/c114/roll.ts index 76fbc4b1a1b5..322f59ed7d6c 100644 --- a/lib/routes/c114/roll.ts +++ b/lib/routes/c114/roll.ts @@ -9,7 +9,7 @@ import timezone from '@/utils/timezone'; export const handler = async (ctx) => { const { original = 'false' } = 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 = 'https://www.c114.com.cn'; const currentUrl = new URL(`news/roll.asp${original === 'true' ? '?o=true' : ''}`, rootUrl).href; @@ -30,7 +30,7 @@ export const handler = async (ctx) => { return { title: item.find('h6 a').text(), - pubDate: timezone(parseDate(item.find('div.new_list_time').text(), ['HH:mm', 'M/D']), +8), + pubDate: timezone(parseDate(item.find('div.new_list_time').text(), ['HH:mm', 'M/D']), 8), link: new URL(item.find('h6 a').prop('href'), rootUrl).href, author: item.find('div.new_list_author').text().trim(), language, @@ -51,7 +51,7 @@ export const handler = async (ctx) => { item.title = title; item.description = description; - item.pubDate = timezone(parseDate($$('div.r_time').text(), 'YYYY/M/D HH:mm'), +8); + item.pubDate = timezone(parseDate($$('div.r_time').text(), 'YYYY/M/D HH:mm'), 8); item.author = $$('div.author').first().text().trim(); item.content = { html: description, diff --git a/lib/routes/caai/utils.tsx b/lib/routes/caai/utils.tsx index dbbee5e7a42b..ea339e7c6ae3 100644 --- a/lib/routes/caai/utils.tsx +++ b/lib/routes/caai/utils.tsx @@ -28,7 +28,7 @@ const fetchAllArticles = (data) => { const r = { title: c.find('h3 a[href]').text().trim(), link: base + c.find('h3 a[href]').attr('href'), - pubDate: timezone(parseDate(c.find('h4').text().trim(), 'YYYY-MM-DD'), +8), + pubDate: timezone(parseDate(c.find('h4').text().trim(), 'YYYY-MM-DD'), 8), }; return r; }); diff --git a/lib/routes/caam/index.ts b/lib/routes/caam/index.ts index 9753cb9086f4..79090a018c82 100644 --- a/lib/routes/caam/index.ts +++ b/lib/routes/caam/index.ts @@ -14,7 +14,7 @@ export const route: Route = { async function handler(ctx) { const { category = '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 rootUrl = 'http://www.caam.org.cn'; const currentUrl = new URL(`chn/1/cate_${category}/list_1.html`, rootUrl).href; diff --git a/lib/routes/cahkms/index.tsx b/lib/routes/cahkms/index.tsx index 62d9585ad48c..a2829625357d 100644 --- a/lib/routes/cahkms/index.tsx +++ b/lib/routes/cahkms/index.tsx @@ -68,7 +68,7 @@ async function handler(ctx) { .map((item) => ({ title: item.TITLE, description: `

${item.GJZ}

`, - pubDate: timezone(parseDate(item.JDRQ), +8), + pubDate: timezone(parseDate(item.JDRQ), 8), link: `${rootUrl}/HKMAC/indexMac/getWzxx?id=${item.ID}`, })); diff --git a/lib/routes/caijing/roll.ts b/lib/routes/caijing/roll.ts index eac82084e240..fa8bd8d0fa3d 100644 --- a/lib/routes/caijing/roll.ts +++ b/lib/routes/caijing/roll.ts @@ -42,7 +42,7 @@ async function handler() { const list = response.data.map((item) => ({ title: item.title, link: item.url.replace('http://', 'https://'), - pubDate: timezone(parseDate(item.published, 'MM-DD HH:mm'), +8), + pubDate: timezone(parseDate(item.published, 'MM-DD HH:mm'), 8), category: item.cat, })); diff --git a/lib/routes/caixin/blog.ts b/lib/routes/caixin/blog.ts index 0a35b557dbd3..ce118899bf79 100644 --- a/lib/routes/caixin/blog.ts +++ b/lib/routes/caixin/blog.ts @@ -41,7 +41,7 @@ async function handler(ctx) { const user = $('div.indexMainConri > script[type="text/javascript"]') .text() .slice('window.user = '.length + 1) - .split(';')[0] + .split(';', 1)[0] .replaceAll(/\s/g, ''); const authorId = user.match(/id:"(\d+)"/)[1]; const authorName = user.match(/name:"(.*?)"/)[1]; @@ -77,28 +77,27 @@ async function handler(ctx) { image: avatar, item: items, }; - } else { - const { data } = await got('https://blog.caixin.com/blog-api/post/index', { - searchParams: { - page: 1, - size: limit, - }, - }); - const posts = data.data.map((item) => ({ - title: item.title, - description: item.brief, - author: item.authorName, - link: item.postUrl.replace('http://', 'https://'), - pubDate: parseDate(item.publishTime, 'x'), - })); - const items = await Promise.all(posts.map((item) => cache.tryGet(item.link, () => parseBlogArticle(item)))); - - return { - title: '财新博客 - 全部', - link: 'https://blog.caixin.com', - // description: introduce, - // image: avatar, - item: items, - }; } + const { data } = await got('https://blog.caixin.com/blog-api/post/index', { + searchParams: { + page: 1, + size: limit, + }, + }); + const posts = data.data.map((item) => ({ + title: item.title, + description: item.brief, + author: item.authorName, + link: item.postUrl.replace('http://', 'https://'), + pubDate: parseDate(item.publishTime, 'x'), + })); + const items = await Promise.all(posts.map((item) => cache.tryGet(item.link, () => parseBlogArticle(item)))); + + return { + title: '财新博客 - 全部', + link: 'https://blog.caixin.com', + // description: introduce, + // image: avatar, + item: items, + }; } diff --git a/lib/routes/caixin/category.ts b/lib/routes/caixin/category.ts index 0fd9f0e2072b..081c7b557f66 100644 --- a/lib/routes/caixin/category.ts +++ b/lib/routes/caixin/category.ts @@ -60,7 +60,7 @@ async function handler(ctx) { const entity = JSON.parse( $('script') .text() - .match(/var entity = ({.*?})/)[1] + .match(/var entity = (\{.*?\})/)[1] ); const { @@ -79,7 +79,7 @@ async function handler(ctx) { title: item.desc, description: item.summ, link: item.link.replace('http://', 'https://'), - pubDate: timezone(parseDate(item.time), +8), + pubDate: timezone(parseDate(item.time), 8), category: item.keyword.split(' '), audio: item.audioUrl, audio_image_url: item.pict.imgs[0].url, diff --git a/lib/routes/caixin/database.ts b/lib/routes/caixin/database.ts index 2c2180013de6..63eebef05b2d 100644 --- a/lib/routes/caixin/database.ts +++ b/lib/routes/caixin/database.ts @@ -55,7 +55,7 @@ async function handler() { const detailResponse = await got(item.link); const content = load(detailResponse.data); - item.pubDate = timezone(parseDate(content('#pubtime_baidu').text()), +8); + item.pubDate = timezone(parseDate(content('#pubtime_baidu').text()), 8); item.description = renderArticle({ item, $: content, diff --git a/lib/routes/caixin/utils-fulltext.ts b/lib/routes/caixin/utils-fulltext.ts index e17c54b6b391..af5b02716165 100644 --- a/lib/routes/caixin/utils-fulltext.ts +++ b/lib/routes/caixin/utils-fulltext.ts @@ -14,7 +14,7 @@ export async function getFulltext(url: string) { if (!config.caixin.cookie) { return; } - if (!/(\d+)\.html/.test(url)) { + if (!/\d+\.html/.test(url)) { return; } const articleID = url.match(/(\d+)\.html/)[1]; @@ -24,7 +24,7 @@ export async function getFulltext(url: string) { const userID = config.caixin.cookie .split(';') .find((e) => e.includes('SA_USER_UID')) - ?.split('=')[1]; // + ?.split('=', 2)[1]; // const rawString = `id=${articleID}&uid=${userID}&${nonce}=nonce`; diff --git a/lib/routes/caixin/utils.ts b/lib/routes/caixin/utils.ts index fd9be6dee05c..73e7e547d514 100644 --- a/lib/routes/caixin/utils.ts +++ b/lib/routes/caixin/utils.ts @@ -7,24 +7,23 @@ import { renderArticle } from './templates/article'; const parseArticle = async (item) => { if (new URL(item.link).hostname.endsWith('.blog.caixin.com')) { return parseBlogArticle(item); - } else { - const { data: response } = await got(item.link); - - const $ = load(response); + } + const { data: response } = await got(item.link); - item.description = renderArticle({ - item, - $, - }); + const $ = load(response); - if (item.audio) { - item.itunes_item_image = item.audio_image_url; - item.enclosure_url = item.audio; - item.enclosure_type = 'audio/mpeg'; - } + item.description = renderArticle({ + item, + $, + }); - return item; + if (item.audio) { + item.itunes_item_image = item.audio_image_url; + item.enclosure_url = item.audio; + item.enclosure_type = 'audio/mpeg'; } + + return item; }; const parseBlogArticle = async (item) => { diff --git a/lib/routes/caixin/weekly.ts b/lib/routes/caixin/weekly.ts index c67ef5b04c12..89d1c9596ed8 100644 --- a/lib/routes/caixin/weekly.ts +++ b/lib/routes/caixin/weekly.ts @@ -37,7 +37,7 @@ async function handler(ctx) { .map((item) => ({ link: $(item).attr('href'), })), - ].slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10) as DataItem[]; + ].slice(0, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 10) as DataItem[]; const items = (await Promise.all( list.map((item) => diff --git a/lib/routes/caixinglobal/latest.ts b/lib/routes/caixinglobal/latest.ts index 87b02b2384cb..1df291e168a3 100644 --- a/lib/routes/caixinglobal/latest.ts +++ b/lib/routes/caixinglobal/latest.ts @@ -34,7 +34,7 @@ async function handler(ctx) { searchParams: { subject: '100990318;100990314;100990311', start: 0, - count: ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20, + count: ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20, type: '2', _: Date.now(), }, diff --git a/lib/routes/cankaoxiaoxi/index.tsx b/lib/routes/cankaoxiaoxi/index.tsx index 2f29816ffd3e..a2069e2186bf 100644 --- a/lib/routes/cankaoxiaoxi/index.tsx +++ b/lib/routes/cankaoxiaoxi/index.tsx @@ -68,7 +68,7 @@ async function handler(ctx) { title: item.data.title, author: item.data.userName, category: item.data.channelName, - pubDate: timezone(parseDate(item.data.publishTime), +8), + pubDate: timezone(parseDate(item.data.publishTime), 8), link: item.data.moVideoPath ? item.data.sourceUrl : `${rootUrl}/json/content/${item.data.url.match(/\/pages\/(.*?)\.html/)[1]}.detailjson`, video: item.data.moVideoPath, cover: item.data.mCoverImg, @@ -87,7 +87,7 @@ async function handler(ctx) { const data = detailResponse.data; - item.link = `${rootUrl}/#/detailsPage/${id}/${data.id}/1/${data.publishTime.split(' ')[0]}`; + item.link = `${rootUrl}/#/detailsPage/${id}/${data.id}/1/${data.publishTime.split(' ', 1)[0]}`; item.description = data.txt; } diff --git a/lib/routes/capitalmind/utils.ts b/lib/routes/capitalmind/utils.ts index dc04f8b14e28..08fb49362067 100644 --- a/lib/routes/capitalmind/utils.ts +++ b/lib/routes/capitalmind/utils.ts @@ -24,7 +24,7 @@ export async function fetchArticles(path) { .text() .trim(); const image = $element.find('img').attr('src'); - const imageUrl = image?.startsWith('/_next/image') ? image.split('url=')[1].split('&')[0] : image; + const imageUrl = image?.startsWith('/_next/image') ? image.split('url=', 2)[1].split('&', 1)[0] : image; const decodedImageUrl = imageUrl ? decodeURIComponent(imageUrl) : ''; // Fetch full article content diff --git a/lib/routes/cara/likes.ts b/lib/routes/cara/likes.ts index e877cf30eb91..d69b7b89d749 100644 --- a/lib/routes/cara/likes.ts +++ b/lib/routes/cara/likes.ts @@ -7,7 +7,7 @@ import type { PostsResponse } from './types'; import { customFetch, parseUserData } from './utils'; export const route: Route = { - path: ['/likes/:user'], + path: '/likes/:user', categories: ['social-media'], example: '/cara/likes/fengz', parameters: { user: 'username' }, @@ -24,7 +24,7 @@ export const route: Route = { async function handler(ctx): Promise { const user = ctx.req.param('user'); - 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 userInfo = await parseUserData(user); const api = `${API_HOST}/posts/getAllLikesByUser?slug=${userInfo.slug}&take=${limit}`; diff --git a/lib/routes/cara/portfolio.ts b/lib/routes/cara/portfolio.ts index 48e57f2f7bdd..89cae98773a6 100644 --- a/lib/routes/cara/portfolio.ts +++ b/lib/routes/cara/portfolio.ts @@ -6,7 +6,7 @@ import type { PortfolioResponse } from './types'; import { customFetch, fetchPortfolioItem, parseUserData } from './utils'; export const route: Route = { - path: ['/portfolio/:user'], + path: '/portfolio/:user', categories: ['social-media'], example: '/cara/portfolio/fengz', parameters: { user: 'username' }, @@ -23,7 +23,7 @@ export const route: Route = { async function handler(ctx): Promise { const user = ctx.req.param('user'); - 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 userInfo = await parseUserData(user); const api = `${API_HOST}/profiles/portfolio?id=${userInfo.id}&take=${limit}`; diff --git a/lib/routes/cara/timeline.ts b/lib/routes/cara/timeline.ts index 523d0b1008b8..283ec9113593 100644 --- a/lib/routes/cara/timeline.ts +++ b/lib/routes/cara/timeline.ts @@ -7,7 +7,7 @@ import type { PostsResponse } from './types'; import { customFetch, parseUserData } from './utils'; export const route: Route = { - path: ['/timeline/:user'], + path: '/timeline/:user', categories: ['social-media'], example: '/cara/timeline/fengz', parameters: { user: 'username' }, @@ -24,7 +24,7 @@ export const route: Route = { async function handler(ctx): Promise { const user = ctx.req.param('user'); - 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 userInfo = await parseUserData(user); const api = `${API_HOST}/posts/getAllByUser?slug=${userInfo.slug}&take=${limit}`; diff --git a/lib/routes/carousell/index.ts b/lib/routes/carousell/index.ts index 8657d8853759..f1b39f03aeda 100644 --- a/lib/routes/carousell/index.ts +++ b/lib/routes/carousell/index.ts @@ -265,7 +265,7 @@ async function handler(ctx): Promise { const siteResponse = await ofetch.raw(baseUrl); const cookies = siteResponse.headers .getSetCookie() - ?.map((c) => c.split(';')[0]) + ?.map((c) => c.split(';', 1)[0]) .join('; '); const csrfToken = siteResponse._data.match(/"csrfToken":"(.*?)","/)[1]; diff --git a/lib/routes/cartoonmad/comic.tsx b/lib/routes/cartoonmad/comic.tsx index 61e24bf8960c..a8009be8cd04 100644 --- a/lib/routes/cartoonmad/comic.tsx +++ b/lib/routes/cartoonmad/comic.tsx @@ -27,10 +27,10 @@ const loadContent = (id, { chapter, pages }) => { return description; }; -const getChapters = (id, list, tryGet) => +const getChapters = (id, list) => Promise.all( list.map((item) => - tryGet(item.link, () => { + cache.tryGet(item.link, () => { item.description = loadContent(id, item); return item; @@ -91,7 +91,7 @@ async function handler(ctx) { }) .toReversed(); - const chapters = await getChapters(id, list, cache.tryGet); + const chapters = await getChapters(id, list); return { title: $('head title').text(), diff --git a/lib/routes/cas/iee/kydt.ts b/lib/routes/cas/iee/kydt.ts index 8d5e23ffc15e..a777b1ebf344 100644 --- a/lib/routes/cas/iee/kydt.ts +++ b/lib/routes/cas/iee/kydt.ts @@ -60,7 +60,7 @@ async function handler() { const content = load(detailResponse.data); item.description = content('.article-content').html(); - item.pubDate = timezone(parseDate(content('time').text().split(':')[1]), 8); + item.pubDate = timezone(parseDate(content('time').text().split(':', 2)[1]), 8); return item; }) diff --git a/lib/routes/cas/sim/kyjz.ts b/lib/routes/cas/sim/kyjz.ts index 116fc6ccb316..1e61deea23fd 100644 --- a/lib/routes/cas/sim/kyjz.ts +++ b/lib/routes/cas/sim/kyjz.ts @@ -55,7 +55,7 @@ async function handler() { const $ = load(response.data); const author = $('.qtinfo.hidden-lg.hidden-md.hidden-sm').text(); - const reg = /文章来源:(.*?)\|/g; + const reg = /文章来源:(.*?)\|/; item.title = $('p.wztitle').text().trim(); item.author = reg.exec(author)[1].toString().trim(); diff --git a/lib/routes/casssp/news.ts b/lib/routes/casssp/news.ts index ec9e359fbb69..65a36ed31a70 100644 --- a/lib/routes/casssp/news.ts +++ b/lib/routes/casssp/news.ts @@ -28,7 +28,7 @@ export const route: Route = { async function handler(ctx) { const { category = '3' } = 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.casssp.org.cn'; const currentUrl = new URL(`news/${category}/`, rootUrl).href; diff --git a/lib/routes/cast/index.ts b/lib/routes/cast/index.ts index dd8e0f34f6d4..32732e8e1fde 100644 --- a/lib/routes/cast/index.ts +++ b/lib/routes/cast/index.ts @@ -27,7 +27,7 @@ async function parsePage(html: string) { return cache.tryGet(articleUrl, async () => { const res = await got.get(articleUrl!); const article = load(res.data); - const pubDate = timezone(parseDate(article('meta[name=PubDate]').attr('content')!, 'YYYY-MM-DD HH:mm'), +8); + const pubDate = timezone(parseDate(article('meta[name=PubDate]').attr('content')!, 'YYYY-MM-DD HH:mm'), 8); return { title: title.text(), diff --git a/lib/routes/castbox/channel.ts b/lib/routes/castbox/channel.ts index cfda05902718..57940fe64d9a 100644 --- a/lib/routes/castbox/channel.ts +++ b/lib/routes/castbox/channel.ts @@ -9,7 +9,7 @@ const PERMUTAION_MAP = [24, 13, 4, 19, 6, 0, 8, 21, 25, 7, 28, 1, 15, 31, 10, 9, const getNonce = (params: Record) => { const m = new Date().toISOString().slice(0, 10).replaceAll('-', ''); - const sortedKeys = Object.keys(params).toSorted(); + const sortedKeys = Object.keys(params).toSorted((a, b) => a.localeCompare(b)); const queryParts = sortedKeys.map((k) => `${k}=${params[k]}`); const queryStr = queryParts.join('&'); @@ -50,7 +50,7 @@ You can use the RSSHub global \`limit\` query parameter to specify the maximum n maintainers: ['ananyatimalsina'], handler: async (ctx) => { const { channel } = ctx.req.param(); - const cid = channel.split('-id')[1]; + const cid = channel.split('-id', 2)[1]; if (!cid) { throw new Error('Invalid channel format. Missing -id'); @@ -66,7 +66,7 @@ You can use the RSSHub global \`limit\` query parameter to specify the maximum n } const chData = channelData.data; - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit') as string, 10) : 50; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit') as string) : 50; const epParams = { cid, limit, r: 1, raw: 1, web: 1 }; const { m: em, n: en, queryStr: eQuery } = getNonce(epParams); diff --git a/lib/routes/cbaigui/index.ts b/lib/routes/cbaigui/index.ts index 9a3872640c96..b07d491428fe 100644 --- a/lib/routes/cbaigui/index.ts +++ b/lib/routes/cbaigui/index.ts @@ -16,7 +16,7 @@ export const route: Route = { }; async function handler(ctx) { - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 50; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 50; let filterName; @@ -63,7 +63,7 @@ async function handler(ctx) { content('p img').each((_, el) => { const image = content(el); - const src = image.prop('src').split('!')[0]; + const src = image.prop('src').split('!', 1)[0]; const width = image.prop('width'); const height = image.prop('height'); diff --git a/lib/routes/cbirc/index.ts b/lib/routes/cbirc/index.ts deleted file mode 100644 index 1f6d8e3bd916..000000000000 --- a/lib/routes/cbirc/index.ts +++ /dev/null @@ -1,133 +0,0 @@ -import type { Route } from '@/types'; -import cache from '@/utils/cache'; -import got from '@/utils/got'; - -const categories = { - jgdt: { - baseUrl: 'http://www.cbirc.gov.cn', - description: '监管动态', - link: 'http://www.cbirc.gov.cn/cn/static/data/DocInfo/SelectDocByItemIdAndChild/data_itemId=915,pageIndex=1,pageSize=18.json', - title: '监管动态', - }, - ggtz: { - baseUrl: 'http://www.cbirc.gov.cn', - description: '公告通知', - link: 'http://www.cbirc.gov.cn/cn/static/data/DocInfo/SelectDocByItemIdAndChild/data_itemId=925,pageIndex=1,pageSize=18.json', - title: '公告通知', - }, - zcfg: { - baseUrl: 'http://www.cbirc.gov.cn', - description: '政策法规', - link: 'http://www.cbirc.gov.cn/cn/static/data/DocInfo/SelectDocByItemIdAndChild/data_itemId=926,pageIndex=1,pageSize=18.json', - title: '政策法规', - }, - zcjd: { - baseUrl: 'http://www.cbirc.gov.cn', - description: '政策解读', - link: 'http://www.cbirc.gov.cn/cn/static/data/DocInfo/SelectDocByItemIdAndChild/data_itemId=916,pageIndex=1,pageSize=18.json', - title: '政策解读', - }, - zqyj: { - baseUrl: 'http://www.cbirc.gov.cn', - description: '征求意见', - link: 'http://www.cbirc.gov.cn/cn/static/data/DocInfo/SelectDocByItemIdAndChild/data_itemId=951,pageIndex=1,pageSize=18.json', - title: '征求意见', - }, - xzxk: { - baseUrl: 'http://www.cbirc.gov.cn', - description: '行政许可', - link: 'http://www.cbirc.gov.cn/cn/static/data/DocInfo/SelectDocByItemIdAndChild/data_itemId=930,pageIndex=1,pageSize=18.json', - title: '行政许可', - }, - xzcf: { - baseUrl: 'http://www.cbirc.gov.cn', - description: '行政处罚', - link: 'http://www.cbirc.gov.cn/cn/static/data/DocInfo/SelectDocByItemIdAndChild/data_itemId=931,pageIndex=1,pageSize=18.json', - title: '行政处罚', - }, - xzjgcs: { - baseUrl: 'http://www.cbirc.gov.cn', - description: '行政监管措施', - link: 'http://www.cbirc.gov.cn/cn/static/data/DocInfo/SelectDocByItemIdAndChild/data_itemId=932,pageIndex=1,pageSize=18.json', - title: '行政监管措施', - }, - gzlw: { - baseUrl: 'http://www.cbirc.gov.cn', - description: '工作论文', - link: 'http://www.cbirc.gov.cn/cn/static/data/DocInfo/SelectDocByItemIdAndChild/data_itemId=934,pageIndex=1,pageSize=18.json', - title: '工作论文', - }, - jrzgyj: { - baseUrl: 'http://www.cbirc.gov.cn', - description: '金融监管研究', - link: 'http://www.cbirc.gov.cn/cn/static/data/DocInfo/SelectDocByItemIdAndChild/data_itemId=935,pageIndex=1,pageSize=18.json', - title: '金融监管研究', - }, - tjxx: { - baseUrl: 'http://www.cbirc.gov.cn', - description: '统计信息', - link: 'http://www.cbirc.gov.cn/cn/static/data/DocInfo/SelectDocByItemIdAndChild/data_itemId=954,pageIndex=1,pageSize=18.json', - title: '统计信息', - }, -}; - -async function getContent(item) { - const response = await got({ - method: 'get', - url: 'http://www.cbirc.gov.cn/cn/static/data/DocInfo/SelectByDocId/data_docId=' + item.docId + '.json', - }); - return response.data.data.docClob; -} - -export const route: Route = { - path: '/:category?', - radar: [ - { - source: ['cbirc.gov.cn/:category', 'cbirc.gov.cn/'], - }, - ], - name: 'Unknown', - maintainers: ['JkCheung'], - handler, -}; - -async function handler(ctx) { - const category = ctx.req.param('category') ?? 'ggtz'; - const cat = categories[category]; - - // 请求集合 - const response = await cache.tryGet(cat.link, async () => { - const resp = await got({ - method: 'get', - url: cat.link, - headers: { - Referer: 'http://www.cbirc.gov.cn', - }, - }); - return resp.data; - }); - - // 遍历数据集合 - const dataLs = await Promise.all( - response.data.rows.map(async (item) => { - const content = await getContent(item); - return { - title: item.docTitle, - // 文章正文 - description: content, - // 文章发布时间 - pubDate: item.publishDate, - // 文章链接 - link: `http://www.cbirc.gov.cn/cn/view/pages/ItemDetail.html?docId=${item.docId}&itemId=925&generaltype=0`, - }; - }) - ); - - return { - title: `中国银保监会-${cat.title}`, - link: cat.link, - description: `中国银保监会-${cat.title}`, - item: dataLs, - language: 'zh-CN', - }; -} diff --git a/lib/routes/cbndata/information.ts b/lib/routes/cbndata/information.ts index 0cbcbb6f2f88..54fff60d859f 100644 --- a/lib/routes/cbndata/information.ts +++ b/lib/routes/cbndata/information.ts @@ -12,7 +12,7 @@ import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { id = 'all' } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '50', 10); + const limit = Number(ctx.req.query('limit') ?? '50'); const baseUrl = 'https://www.cbndata.com'; const targetUrl: string = new URL(`information?tag_id=${id}`, baseUrl).href; diff --git a/lib/routes/cbpanet/index.ts b/lib/routes/cbpanet/index.ts index 7aa617d5f230..1694fee9c35c 100644 --- a/lib/routes/cbpanet/index.ts +++ b/lib/routes/cbpanet/index.ts @@ -8,7 +8,7 @@ import timezone from '@/utils/timezone'; export const handler = async (ctx) => { const { bigId = '2', smallId = '11' } = 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 = 'https://www.cbpanet.com'; const currentUrl = new URL(`dzp_news.aspx?bigid=${bigId}&smallid=${smallId}`, rootUrl).href; @@ -52,7 +52,7 @@ export const handler = async (ctx) => { .replace(/发布时间:/, ''), 'YYYY/M/D HH:mm:ss' ), - +8 + 8 ); item.content = { html: description, @@ -73,7 +73,7 @@ export const handler = async (ctx) => { item: items, allowEmpty: true, image, - author: title.split(/-/)[0], + author: title.split(/-/, 1)[0], language, }; }; diff --git a/lib/routes/ccac/news.ts b/lib/routes/ccac/news.ts index cf893ee8e771..508c196ce03a 100644 --- a/lib/routes/ccac/news.ts +++ b/lib/routes/ccac/news.ts @@ -32,21 +32,21 @@ export const route: Route = { }; async function handler(ctx) { - const browser = await playwright(); + const context = await playwright(); const lang = ctx.req.param('lang') ?? 'sc'; const type = utils.TYPE[ctx.req.param('type')]; const BASE = utils.langBase(lang); - 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(BASE, { waitUntil: 'domcontentloaded', }); const articles = await page.evaluate(() => window.articles); - await browser.close(); + await context.close(); const list = utils .typeFilter(articles, type) diff --git a/lib/routes/ccagm/index.ts b/lib/routes/ccagm/index.ts index 62f3bd55aa11..86fdb8ae1f97 100644 --- a/lib/routes/ccagm/index.ts +++ b/lib/routes/ccagm/index.ts @@ -12,7 +12,7 @@ import timezone from '@/utils/timezone'; export const handler = async (ctx: Context): Promise => { const { category = 'association-news' } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '10', 10); + const limit = Number(ctx.req.query('limit') ?? '10'); const baseUrl = 'http://www.ccagm.org.cn'; const targetUrl: string = new URL(category, baseUrl).href; @@ -34,9 +34,9 @@ export const handler = async (ctx: Context): Promise => { const processedItem: DataItem = { title, - pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : undefined, + pubDate: pubDateStr ? timezone(parseDate(pubDateStr), 8) : undefined, link: linkUrl, - updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : undefined, + updated: upDatedStr ? timezone(parseDate(upDatedStr), 8) : undefined, language, }; @@ -61,12 +61,12 @@ export const handler = async (ctx: Context): Promise => { const processedItem: DataItem = { title, description, - pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : item.pubDate, + pubDate: pubDateStr ? timezone(parseDate(pubDateStr), 8) : item.pubDate, content: { html: description, text: description, }, - updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : item.updated, + updated: upDatedStr ? timezone(parseDate(upDatedStr), 8) : item.updated, language, }; diff --git a/lib/routes/cccfna/index.ts b/lib/routes/cccfna/index.ts index 0ff78618e9d9..dade94534c8f 100644 --- a/lib/routes/cccfna/index.ts +++ b/lib/routes/cccfna/index.ts @@ -66,7 +66,7 @@ export const route: Route = { const content = $('.list_cont'); item.title = content.find('.title').text(); - item.pubDate = timezone(parseDate(content.find('.tip > .time').text(), '发布时间:YYYY-MM-DD'), +8); + item.pubDate = timezone(parseDate(content.find('.tip > .time').text(), '发布时间:YYYY-MM-DD'), 8); item.description = content.find('#article-content').html()!; return item; diff --git a/lib/routes/cccmc/index.ts b/lib/routes/cccmc/index.ts index 2f5354f8afc3..188447cd7205 100644 --- a/lib/routes/cccmc/index.ts +++ b/lib/routes/cccmc/index.ts @@ -11,7 +11,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx: Context): Promise => { const { category = 'ywgg/tzgg' } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '15', 10); + const limit = Number(ctx.req.query('limit') ?? '15'); const baseUrl = 'https://www.cccmc.org.cn'; const targetUrl: string = new URL(category.endsWith('/') ? category : `${category}/`, baseUrl).href; @@ -95,7 +95,7 @@ export const handler = async (ctx: Context): Promise => { return { title, - description: title.split(/-/)[0].trim(), + description: title.split(/-/, 1)[0].trim(), link: targetUrl, item: items, allowEmpty: true, diff --git a/lib/routes/ccf/tfbd/utils.tsx b/lib/routes/ccf/tfbd/utils.tsx index bebaced5a9a0..69666d278191 100644 --- a/lib/routes/ccf/tfbd/utils.tsx +++ b/lib/routes/ccf/tfbd/utils.tsx @@ -29,7 +29,7 @@ const fetchAllArticles = (data) => { const r = { title: c.find('h3 a[href]').text().trim(), link: base + c.find('h3 a[href]').attr('href'), - pubDate: timezone(parseDate(c.find('p').text().trim(), 'YYYY-MM-DD'), +8), + pubDate: timezone(parseDate(c.find('p').text().trim(), 'YYYY-MM-DD'), 8), }; return r; }); diff --git a/lib/routes/ccfa/index.tsx b/lib/routes/ccfa/index.tsx index a497182bee3a..0b9332cc423d 100644 --- a/lib/routes/ccfa/index.tsx +++ b/lib/routes/ccfa/index.tsx @@ -9,7 +9,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx) => { const { type = '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 rootUrl = 'http://www.ccfa.org.cn'; const currentUrl = new URL(`portal/cn/xiehui_list.jsp?type=${type}`, rootUrl).href; diff --git a/lib/routes/ccg/index.ts b/lib/routes/ccg/index.ts index d53458f64e56..714c86cbaa6c 100644 --- a/lib/routes/ccg/index.ts +++ b/lib/routes/ccg/index.ts @@ -13,7 +13,7 @@ import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category = 'news' } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '7', 10); + const limit = Number(ctx.req.query('limit') ?? '7'); const baseUrl = 'http://www.ccg.org.cn'; const targetUrl: string = new URL(category, baseUrl).href; diff --git a/lib/routes/ccmn/price-adjustment.tsx b/lib/routes/ccmn/price-adjustment.tsx index 830434cee1c8..cf11a670ef3a 100644 --- a/lib/routes/ccmn/price-adjustment.tsx +++ b/lib/routes/ccmn/price-adjustment.tsx @@ -86,12 +86,12 @@ const READABLE_CATEGORIES = { async function handler(ctx) { const category = ctx.req.param('category'); - const subDomain = SUB_DOMAIN_MAP[category]; - if (!subDomain) { + const subdomain = SUB_DOMAIN_MAP[category]; + if (!subdomain) { throw new Error('未知的金属类型'); } - const url = `https://${subDomain}`; + const url = `https://${subdomain}`; const response = await got({ method: 'get', @@ -133,7 +133,7 @@ async function handler(ctx) { .trim(); const changeStr = $avgSpan.find('.up_down').text().trim(); - const changeNum = Number.parseFloat(changeStr); + const changeNum = Number(changeStr); let icon; // 如果 change 为 0 或者无法解析,则不显示图标 if (changeNum > 0) { @@ -191,7 +191,7 @@ async function handler(ctx) { description, link: `${url}/quota/${dataId}.html`, guid: dataId, // 使用 data-id 作为唯一标识 - pubDate: timezone(parseDate(dateStr, 'MM-DD'), +8), + pubDate: timezone(parseDate(dateStr, 'MM-DD'), 8), }; }); diff --git a/lib/routes/ccreports/index.ts b/lib/routes/ccreports/index.ts index 969e3e137ba6..34ea40c58473 100644 --- a/lib/routes/ccreports/index.ts +++ b/lib/routes/ccreports/index.ts @@ -53,7 +53,7 @@ async function handler() { const detailData = await got.get(item.link); const $ = load(detailData.data); item.description = $('div.pdbox').html(); - item.pubDate = timezone(parseDate($('div.newbox > div.newtit > p').text(), 'YYYY-MM-DD HH:mm:ss'), +8); + item.pubDate = timezone(parseDate($('div.newbox > div.newtit > p').text(), 'YYYY-MM-DD HH:mm:ss'), 8); return item; }) diff --git a/lib/routes/cctv/category.ts b/lib/routes/cctv/category.ts index 6b018770ad6d..90026d7aa999 100644 --- a/lib/routes/cctv/category.ts +++ b/lib/routes/cctv/category.ts @@ -1,8 +1,8 @@ import type { Route } from '@/types'; -import getMzzlbg from './utils/mzzlbg'; -import getNews from './utils/news'; -import xinwen1j1 from './utils/xinwen1j1'; +import { getMzzlbg } from './utils/mzzlbg'; +import { getNews } from './utils/news'; +import { xinwen1j1 } from './utils/xinwen1j1'; import getXWLB from './xwlb'; export const route: Route = { diff --git a/lib/routes/cctv/lm.ts b/lib/routes/cctv/lm.ts index f3b856f32f99..556fe12b643e 100644 --- a/lib/routes/cctv/lm.ts +++ b/lib/routes/cctv/lm.ts @@ -70,7 +70,7 @@ async function handler(ctx) { guid: item.guid, image: item.image, title: item.title, - pubDate: timezone(parseDate(item.time), +8), + pubDate: timezone(parseDate(item.time), 8), link: `${vdnRootUrl}/api/getHttpVideoInfo.do?pid=${item.guid}`, description: `

${item.brief.replaceAll('\r\n', '

')}

`, })); @@ -90,8 +90,12 @@ async function handler(ctx) { item.description += `
`; } - for (let i = 2; data.video[`chapters${i}`]; i++) { - for (const c of data.video[`chapters${i}`]) { + for (let i = 2; ; i++) { + const chapters = data.video[`chapters${i}`]; + if (!chapters) { + break; + } + for (const c of chapters) { item.description += `
`; } } diff --git a/lib/routes/cctv/utils/mzzlbg.ts b/lib/routes/cctv/utils/mzzlbg.ts index 74a373b92413..7cd0154ffd70 100644 --- a/lib/routes/cctv/utils/mzzlbg.ts +++ b/lib/routes/cctv/utils/mzzlbg.ts @@ -2,7 +2,7 @@ import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; -const getMzzlbg = async () => { +export const getMzzlbg = async () => { const url = 'https://api.cntv.cn/video/videolistById?serviceId=cbox&vsid=C10354&em=01&p=1&n=50'; const response = await got({ @@ -19,7 +19,7 @@ const getMzzlbg = async () => { title: video.t, description: video.desc, link: video.url, - pubDate: timezone(parseDate(video.ptime), +8), + pubDate: timezone(parseDate(video.ptime), 8), }; const { data: videoDetail } = await got({ method: 'get', @@ -39,4 +39,3 @@ const getMzzlbg = async () => { item: resultItem, }; }; -export default getMzzlbg; diff --git a/lib/routes/cctv/utils/news.ts b/lib/routes/cctv/utils/news.ts index 14ef0ce7f32d..5c5e6455c678 100644 --- a/lib/routes/cctv/utils/news.ts +++ b/lib/routes/cctv/utils/news.ts @@ -6,7 +6,7 @@ import { PRESETS } from '@/utils/header-generator'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; -const getNews = async (category) => { +export const getNews = async (category) => { const url = `https://news.cctv.com/2019/07/gaiban/cmsdatainterface/page/${category}_1.jsonp`; const response = await got({ @@ -26,7 +26,7 @@ const getNews = async (category) => { const item = { title, link: url, - pubDate: timezone(parseDate(focus_date), +8), + pubDate: timezone(parseDate(focus_date), 8), }; const id = path.parse(url).name; const unknownTip = '未知类型,请点击链接提交issue'; @@ -45,7 +45,7 @@ const getNews = async (category) => { type = 'PHO'; } else if (id.startsWith('VIDE')) { // 视频 - const vid = path.parse(image).name.split('-')[0]; + const vid = path.parse(image).name.split('-', 1)[0]; api = `https://vdn.apps.cntv.cn/api/getHttpVideoInfo.do?pid=${vid}`; type = 'VIDE'; } else { @@ -103,4 +103,3 @@ const getNews = async (category) => { item: resultItem, }; }; -export default getNews; diff --git a/lib/routes/cctv/utils/xinwen1j1.ts b/lib/routes/cctv/utils/xinwen1j1.ts index a2072272c17d..aaebe3174c30 100644 --- a/lib/routes/cctv/utils/xinwen1j1.ts +++ b/lib/routes/cctv/utils/xinwen1j1.ts @@ -14,7 +14,7 @@ async function loadContent(link) { // console.log('********') const js_txt = '' + $('script'); - const guid = js_txt.split('guid_Ad_VideoCode = "')[1].split('";')[0]; + const guid = js_txt.split('guid_Ad_VideoCode = "', 2)[1].split('";', 1)[0]; // console.log(guid+' js_txt********') const { data: videoDetail } = await got({ method: 'get', @@ -54,7 +54,7 @@ const ProcessFeed = (data) => return { ...single, ...other }; }) ); -const xinwen1j1 = async () => { +export const xinwen1j1 = async () => { const baseUrl = 'https://api.cntv.cn/NewVideo/getVideoListByColumn?id=TOPC1451559066181661&n=20&sort=desc&p=1&mode=0&serviceId=tvcctv'; // 获取要处理的页面 const res = await got({ @@ -81,4 +81,3 @@ const xinwen1j1 = async () => { item: result, }; }; -export default xinwen1j1; diff --git a/lib/routes/cctv/xwlb.ts b/lib/routes/cctv/xwlb.ts index c22da35122fe..b6dd266a40a1 100644 --- a/lib/routes/cctv/xwlb.ts +++ b/lib/routes/cctv/xwlb.ts @@ -72,19 +72,19 @@ const getXWLB = async () => { const item = { title: `新闻联播 ${newsDate.format('YYYY/MM/DD')}`, link: url, - pubDate: timezone(parseDate(newsDate.format()), +8), + pubDate: timezone(parseDate(newsDate.format()), 8), description: await cache.tryGet(url, async () => { const res = await got(url); const content = load(res.data); - const list: string[] = []; - content('body li').map((i, elem) => { - const e = content(elem); - const href = e.find('a').attr('href'); - const title = e.find('a').attr('title'); - const dur = e.find('span').text(); - list.push(`${title} ⏱${dur}`); - return i; - }); + const list = content('body li') + .toArray() + .map((elem) => { + const e = content(elem); + const href = e.find('a').attr('href'); + const title = e.find('a').attr('title'); + const dur = e.find('span').text(); + return `${title} ⏱${dur}`; + }); return list.join('
\n'); }), }; diff --git a/lib/routes/cdi/index.ts b/lib/routes/cdi/index.ts index f518124bfd9f..65331910ebee 100644 --- a/lib/routes/cdi/index.ts +++ b/lib/routes/cdi/index.ts @@ -69,7 +69,7 @@ async function handler(ctx) { .match(/时间:(.*)/)[1] .replaceAll(/年|月/g, '-') ), - +8 + 8 ); return item; diff --git a/lib/routes/ce/district.ts b/lib/routes/ce/district.ts index 220715b565da..1621eb581db7 100644 --- a/lib/routes/ce/district.ts +++ b/lib/routes/ce/district.ts @@ -77,7 +77,7 @@ async function handler(ctx) { const $ = load(response); const pubDateText = $('span#articleTime').text().trim(); - item.pubDate = timezone(parseDate(pubDateText, 'YYYY年MM月DD日 HH:mm'), +8); + item.pubDate = timezone(parseDate(pubDateText, 'YYYY年MM月DD日 HH:mm'), 8); item.description = $('div.TRS_Editor').html(); return item; diff --git a/lib/routes/cebbank/all.tsx b/lib/routes/cebbank/all.tsx index 66466fcafa69..cd6c59cc263d 100644 --- a/lib/routes/cebbank/all.tsx +++ b/lib/routes/cebbank/all.tsx @@ -62,9 +62,10 @@ async function handler(ctx) { item: items, }; + const pubDate = parseDate($('#t_id span').text().slice(5), 'YYYY-MM-DD HH:mm', true); ctx.set('json', { ...ret, - pubDate: timezone(parseDate($('#t_id span').text().slice(5), 'YYYY-MM-DD HH:mm', true), 0), + pubDate: timezone(pubDate, 0), }); return ret; } diff --git a/lib/routes/cbirc/namespace.ts b/lib/routes/cefco/namespace.ts similarity index 56% rename from lib/routes/cbirc/namespace.ts rename to lib/routes/cefco/namespace.ts index c21982ff11e9..53885a696c25 100644 --- a/lib/routes/cbirc/namespace.ts +++ b/lib/routes/cefco/namespace.ts @@ -1,7 +1,7 @@ import type { Namespace } from '@/types'; export const namespace: Namespace = { - name: '中国银行保险监督管理委员会', - url: 'cbirc.gov.cn', + name: '亚布力中国企业家论坛', + url: 'www.cefco.cn', lang: 'zh-CN', }; diff --git a/lib/routes/cefco/news.ts b/lib/routes/cefco/news.ts new file mode 100644 index 000000000000..3a0ba1355eea --- /dev/null +++ b/lib/routes/cefco/news.ts @@ -0,0 +1,63 @@ +import { load } from 'cheerio'; + +import type { Route } from '@/types'; +import cache from '@/utils/cache'; +import ofetch from '@/utils/ofetch'; +import { parseDate } from '@/utils/parse-date'; +import timezone from '@/utils/timezone'; + +export const route: Route = { + path: '/research/news', + categories: ['other'], + example: '/cefco/research/news', + radar: [ + { + source: ['www.cefco.cn/research/news.html'], + }, + ], + name: '观点', + maintainers: ['TonyRL'], + handler, + url: 'www.cefco.cn/research/news.html', +}; + +async function handler() { + const baseUrl = 'https://www.cefco.cn'; + const link = `${baseUrl}/research/news.html`; + + const response = await ofetch(link); + const $ = load(response); + + const list = $('#ajaxList li.fixed') + .toArray() + .map((item) => { + const $item = $(item); + const a = $item.find('dt a'); + return { + title: a.text().trim(), + link: new URL(a.attr('href')!, baseUrl).href, + description: $item.find('dd.clamp').text(), + pubDate: timezone(parseDate($item.find('dd.time').text().trim(), 'YYYY/MM/DD'), 8), + image: $item.find('a.imgbox img').attr('src') ? new URL($item.find('a.imgbox img').attr('src')!, baseUrl).href : undefined, + }; + }); + + const items = await Promise.all( + list.map((item) => + cache.tryGet(item.link, async () => { + const response = await ofetch(item.link); + const $ = load(response); + item.description = $('.edit_con_original').html() ?? item.description; + return item; + }) + ) + ); + + return { + title: $('head title').text(), + link, + language: 'zh-CN' as const, + image: `${baseUrl}/images/favicon.ico`, + item: items, + }; +} diff --git a/lib/routes/cfachina/analygarden.ts b/lib/routes/cfachina/analygarden.ts index 13bf4c7bc463..78d20a34a0ee 100644 --- a/lib/routes/cfachina/analygarden.ts +++ b/lib/routes/cfachina/analygarden.ts @@ -38,7 +38,7 @@ async function handler(ctx) { pageUrl = `${baseUrl}/servicesupport/analygarden/`; if (program !== '分析师园地') { - pageUrl = `${pageUrl}${program}/`; + pageUrl += `${program}/`; const response = await got(pageUrl); const $ = load(response.data); @@ -59,7 +59,7 @@ async function handler(ctx) { }, searchParams: { pageNo: 1, - pageSize: ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20, + pageSize: ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20, keyword: '', startTime: '', endTime: '', @@ -74,7 +74,7 @@ async function handler(ctx) { title: item.docTitle, author: item.docAuthor, link, - pubDate: timezone(parseDate(item.operTime), +8), + pubDate: timezone(parseDate(item.operTime), 8), enclosure_url: link, enclosure_type: `application/${link.split('.').pop()}`, }; diff --git a/lib/routes/cffex/announcement.ts b/lib/routes/cffex/announcement.ts index ecbac01275d4..7a85667da5aa 100644 --- a/lib/routes/cffex/announcement.ts +++ b/lib/routes/cffex/announcement.ts @@ -49,7 +49,7 @@ async function handler(): Promise<{ title: string; link: string; item: DataItem[ return { title: titleEle.text().trim(), link: `${baseUrl}${titleEle.attr('href')}`, - pubDate: timezone(parseDate(dateEle.text(), 'YYYY-MM-DD'), +8), + pubDate: timezone(parseDate(dateEle.text(), 'YYYY-MM-DD'), 8), }; }); diff --git a/lib/routes/cfmmc/index.ts b/lib/routes/cfmmc/index.ts index 447ca72c8ec7..faa2a6a2420f 100644 --- a/lib/routes/cfmmc/index.ts +++ b/lib/routes/cfmmc/index.ts @@ -15,7 +15,7 @@ export const route: Route = { async function handler(ctx) { const { id = 'main/noticeannouncement/cfmmcnotice' } = 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.cfmmc.com'; const apiUrl = new URL('servlet/json', rootUrl).href; @@ -47,7 +47,7 @@ async function handler(ctx) { response.results?.[0].data.slice(0, limit).map((item) => ({ title: item.title, link: new URL(item.url, rootUrl).href, - pubDate: timezone(parseDate(item.publish_date), +8), + pubDate: timezone(parseDate(item.publish_date), 8), })) ?? []; items = await Promise.all( diff --git a/lib/routes/cfr/utils.ts b/lib/routes/cfr/utils.ts index b9dadd7ccd44..9d7fe078a459 100644 --- a/lib/routes/cfr/utils.ts +++ b/lib/routes/cfr/utils.ts @@ -14,7 +14,7 @@ export function getDataItem(href: string) { const link = `${origin}${href}`; return cache.tryGet(link, async () => { - const prefix = href?.split('/')[1]; + const prefix = href?.split('/', 2)[1]; const res = await ofetch(link); const $ = load(res); diff --git a/lib/routes/cgtn/podcast.ts b/lib/routes/cgtn/podcast.ts index 4354e79f2380..e32de85067f8 100644 --- a/lib/routes/cgtn/podcast.ts +++ b/lib/routes/cgtn/podcast.ts @@ -11,7 +11,7 @@ async function getData(category, id) { function combDate(date, time) { // combine date and time, return a Date object - return timezone(parseDate(date + ' ' + time), +8); + return timezone(parseDate(date + ' ' + time), 8); } export const route: Route = { @@ -45,7 +45,7 @@ async function handler(ctx) { const data = await getData(categoryMap[category], id); const items = data.data.map((item) => ({ title: item.title, - pubDate: combDate(item.showDate, item.time.split(' ')[0]), + pubDate: combDate(item.showDate, item.time.split(' ', 1)[0]), description: item.programSeries.content || item.detail, itunes_item_image: item.programUrl, itunes_duration: item.duration, diff --git a/lib/routes/changba/user.tsx b/lib/routes/changba/user.tsx index 26e6210ebec0..e45b8cbfd15e 100644 --- a/lib/routes/changba/user.tsx +++ b/lib/routes/changba/user.tsx @@ -69,14 +69,14 @@ async function handler(ctx) { return null; } - workid = workid.split("'")[1]; + workid = workid.split("'", 2)[1]; if (!workid) { return null; } const mp3 = `https://upscuw.changba.com/${workid}.mp3`; const description = renderToString(); - const itunes_item_image = $('div.work-cover').attr('style').replace(')', '').split('url(')[1]; + const itunes_item_image = $('div.work-cover').attr('style').replace(')', '').split('url(', 2)[1]; return { title: $('.work-title').text(), description, diff --git a/lib/routes/chiculture/topic.ts b/lib/routes/chiculture/topic.ts index 276df7a1ac48..7c9f95ab319b 100644 --- a/lib/routes/chiculture/topic.ts +++ b/lib/routes/chiculture/topic.ts @@ -58,14 +58,16 @@ async function handler(ctx) { item.pubDate = parseDate(pubDate[1]); } else if (item.title.includes('一周時事通識')) { for (const tag of item.pubDate) { - if (/^\d{4}年$/.test(tag.title)) { - const monthDayStr = item.title.split('- ')[1] ?? item.title.split('-')[1]; - item.pubDate = timezone(parseDate(monthDayStr, 'D/M'), +8); - break; + if (!/^\d{4}年$/.test(tag.title)) { + continue; } + + const monthDayStr = item.title.split('- ', 2)[1] ?? item.title.split('-', 2)[1]; + item.pubDate = timezone(parseDate(monthDayStr, 'D/M'), 8); + break; } } else if (/^\d{4}年新聞回顧$/.test(item.title)) { - item.pubDate = parseDate(`${item.title.split('年')[0]}-12-31`); + item.pubDate = parseDate(`${item.title.split('年', 1)[0]}-12-31`); } else { item.pubDate = ''; } diff --git a/lib/routes/chikubi/navigation.ts b/lib/routes/chikubi/navigation.ts index b4304a5b222c..87424f02cb55 100644 --- a/lib/routes/chikubi/navigation.ts +++ b/lib/routes/chikubi/navigation.ts @@ -46,23 +46,22 @@ async function handler(ctx): Promise { link: `${baseUrl}/best-nipple-article`, item: items, }; - } else { - const { url, title } = navigationItems[keyword]; + } + const { url, title } = navigationItems[keyword]; - const feed = await parser.parseURL(`${baseUrl}${url}/feed`); + const feed = await parser.parseURL(`${baseUrl}${url}/feed`); - const list = feed.items.map((item) => ({ - title: item.title, - link: item.link, - })); + const list = feed.items.map((item) => ({ + title: item.title, + link: item.link, + })); - // 獲取內文 - const items = await processItems(list); + // 獲取內文 + const items = await processItems(list); - return { - title: `${title} - chikubi.jp`, - link: `${baseUrl}${url}`, - item: items, - }; - } + return { + title: `${title} - chikubi.jp`, + link: `${baseUrl}${url}`, + item: items, + }; } diff --git a/lib/routes/chikubi/nipple-video-category.ts b/lib/routes/chikubi/nipple-video-category.ts index 5077f504b093..05e1996ddb2b 100644 --- a/lib/routes/chikubi/nipple-video-category.ts +++ b/lib/routes/chikubi/nipple-video-category.ts @@ -44,7 +44,7 @@ async function handler(ctx): Promise { const items = await processItems(list); return { - title: `動画カテゴリー: ${feed.title?.split('-')[0]} - chikubi.jp`, + title: `動画カテゴリー: ${feed.title?.split('-', 1)[0]} - chikubi.jp`, link: `${baseUrl}${url}`, item: items, }; diff --git a/lib/routes/chikubi/nipple-video-maker.ts b/lib/routes/chikubi/nipple-video-maker.ts index 2d2d1a2ee686..a36def0c86ef 100644 --- a/lib/routes/chikubi/nipple-video-maker.ts +++ b/lib/routes/chikubi/nipple-video-maker.ts @@ -44,7 +44,7 @@ async function handler(ctx): Promise { const items = await processItems(list); return { - title: `AVメーカー: ${feed.title?.split('-')[0]} - chikubi.jp`, + title: `AVメーカー: ${feed.title?.split('-', 1)[0]} - chikubi.jp`, link: `${baseUrl}${url}`, item: items, }; diff --git a/lib/routes/china/finance/finance.ts b/lib/routes/china/finance/finance.ts index 014043c5648b..f4d69fc88bcf 100644 --- a/lib/routes/china/finance/finance.ts +++ b/lib/routes/china/finance/finance.ts @@ -56,7 +56,7 @@ async function handler(ctx) { const $ = load(data); const categoryTitle = $('.list-hd strong').text(); const listCategory = `中华网-财经-${categoryTitle}新闻`; - 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 detailsUrls = $('.item-con-inner') .toArray() .map((item) => { @@ -77,7 +77,7 @@ async function handler(ctx) { title: $d('.article_title').text(), link: item.link, description: $d('#js_article_content').html(), - pubDate: timezone(parseDate($d('.article_info>span.time').text()), +8), + pubDate: timezone(parseDate($d('.article_info>span.time').text()), 8), author: $d(' div.article_info > span.source').text(), category: listCategory, }; diff --git a/lib/routes/chinacdc/index.ts b/lib/routes/chinacdc/index.ts index 5bf1a2d60d5d..1b81fe9b7bf3 100644 --- a/lib/routes/chinacdc/index.ts +++ b/lib/routes/chinacdc/index.ts @@ -13,7 +13,7 @@ import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category = 'zxyw' } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '11', 10); + const limit = Number(ctx.req.query('limit') ?? '11'); const rootUrl = 'https://www.chinacdc.cn'; const targetUrl: string = new URL(category.endsWith('/') ? category : `${category}/`, rootUrl).href; diff --git a/lib/routes/chinadaily/language.ts b/lib/routes/chinadaily/language.ts index dab02950b8a0..dae3316c8acd 100644 --- a/lib/routes/chinadaily/language.ts +++ b/lib/routes/chinadaily/language.ts @@ -14,7 +14,7 @@ import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category = 'thelatest' } = 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://language.chinadaily.com.cn'; const targetUrl: string = new URL(category, baseUrl).href; @@ -96,12 +96,12 @@ export const handler = async (ctx: Context): Promise => { let processedItem: DataItem = { title, - pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : item.pubDate, + pubDate: pubDateStr ? timezone(parseDate(pubDateStr), 8) : item.pubDate, category: categories, author: authors, image, banner: image, - updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : item.updated, + updated: upDatedStr ? timezone(parseDate(upDatedStr), 8) : item.updated, language, }; diff --git a/lib/routes/chinadegrees/province.tsx b/lib/routes/chinadegrees/province.tsx index e32c38d7da5a..e129324fe2f2 100644 --- a/lib/routes/chinadegrees/province.tsx +++ b/lib/routes/chinadegrees/province.tsx @@ -82,11 +82,11 @@ async function handler(ctx) { const data = await 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.resourceType() === 'script' ? 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' ? route.continue() : route.abort(); }); await page.goto(url, { waitUntil: 'domcontentloaded', @@ -94,7 +94,7 @@ async function handler(ctx) { await page.waitForSelector('.datalist'); const html = await page.evaluate(() => document.documentElement.innerHTML); - await browser.close(); + await context.close(); const $ = load(html); return { diff --git a/lib/routes/chinaisa/index.ts b/lib/routes/chinaisa/index.ts index 1323ceaaaba3..e9e44184a9d5 100644 --- a/lib/routes/chinaisa/index.ts +++ b/lib/routes/chinaisa/index.ts @@ -164,7 +164,7 @@ export const route: Route = { async function handler(ctx) { const { id = '58af05dfb6b4300151760176d2aad0a04c275aaadbb1315039263f021f920dcd' } = 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 = 'https://www.chinaisa.org.cn'; @@ -221,7 +221,7 @@ async function handler(ctx) { item.title = content('div.article_title').contents().first().text() || item.title; item.description = content('div.article_main').html(); - item.author = matches[1].split(/&/)[0]; + item.author = matches[1].split(/&/, 1)[0]; item.guid = `chinaisa-${item.guid}`; item.pubDate = parseDate(matches[2]); diff --git a/lib/routes/chinamoney/notice.ts b/lib/routes/chinamoney/notice.ts index df8c23369b11..cf58a1e5f666 100644 --- a/lib/routes/chinamoney/notice.ts +++ b/lib/routes/chinamoney/notice.ts @@ -74,7 +74,7 @@ async function handler(ctx) { const list = contents.records.map((item) => ({ title: item.title, link: `${baseUrl}${item.draftPath}`, - pubDate: timezone(parseDate(item.releaseDate, 'YYYY-MM-DD'), +8), + pubDate: timezone(parseDate(item.releaseDate, 'YYYY-MM-DD'), 8), contentId: item.contentId, })); @@ -101,7 +101,7 @@ async function handler(ctx) { }); item.description = article.html(); - item.pubDate = timezone(parseDate($('.AC-l span').text().trim(), 'YYYY-MM-DD HH:mm'), +8); + item.pubDate = timezone(parseDate($('.AC-l span').text().trim(), 'YYYY-MM-DD HH:mm'), 8); return item; }) @@ -109,7 +109,7 @@ async function handler(ctx) { ); return { - title: `${channels[channelId] ? channels[channelId].title + ' - ' : ''}中国货币网`, + title: `${Object.hasOwn(channels, channelId) ? channels[channelId].title + ' - ' : ''}中国货币网`, link: `${baseUrl}${channels[channelId]?.urlPath ?? ''}`, item: items, }; diff --git a/lib/routes/chinanews/index.ts b/lib/routes/chinanews/index.ts index f8b1a3b833be..883131a784cb 100644 --- a/lib/routes/chinanews/index.ts +++ b/lib/routes/chinanews/index.ts @@ -29,13 +29,14 @@ async function handler(ctx) { url: currentUrl, }); const $ = load(response.data); + const limit = ctx.req.query('limit'); const list = $('a', '.dd_bt') .toArray() .map((item) => ({ link: rootUrl + $(item).attr('href'), title: $(item).text(), })) - .slice(0, ctx.req.query('limit') ? Math.min(Number.parseInt(ctx.req.query('limit')), 125) : 50); + .slice(0, limit ? Number.parseInt(limit) : 50); const items = await Promise.all( list.map((item) => @@ -51,12 +52,12 @@ async function handler(ctx) { item.author = content('a.source').text(); const info = content('p', '.left').text().trim().slice(5).split(' '); item.author = info[2].trim(); - item.pubDate = timezone(parseDate(info[0] + info[1], 'YYYY年MM月DD日HH:mm'), +8); + item.pubDate = timezone(parseDate(info[0] + info[1], 'YYYY年MM月DD日HH:mm'), 8); } else if (content('div.t3').length > 0) { item.description = content('div.t3').html(); const info = content('div[style="text-align:right;font-size:12px;"]').text().slice(5).split(' '); item.author = info[2]; - item.pubDate = timezone(parseDate(info[0] + info[1], 'YYYY-MM-DDHH:mm'), +8); + item.pubDate = timezone(parseDate(info[0] + info[1], 'YYYY-MM-DDHH:mm'), 8); } else { item.description = content('div.left_zw').html(); const info = content('div.left-t') @@ -64,7 +65,7 @@ async function handler(ctx) { .filter((_, el) => el.type === 'text') .text() .split(' '); - item.pubDate = timezone(parseDate(info[0], 'YYYY年MM月DD日 HH:mm'), +8); + item.pubDate = timezone(parseDate(info[0], 'YYYY年MM月DD日 HH:mm'), 8); item.author = info[1] + content('a.source').text(); } return item; diff --git a/lib/routes/chinania/index.ts b/lib/routes/chinania/index.ts index 89a735d74f8f..5f0d51c7869b 100644 --- a/lib/routes/chinania/index.ts +++ b/lib/routes/chinania/index.ts @@ -7,7 +7,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx) => { const { category = 'xiehuidongtai/xiehuitongzhi' } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 25; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 25; const rootUrl = 'https://www.chinania.org.cn'; const currentUrl = new URL(`html/${category.endsWith('/') ? category : `${category}/`}`, rootUrl).href; @@ -62,7 +62,7 @@ export const handler = async (ctx) => { return { title, - description: title.split(/-/)[0]?.trim(), + description: title.split(/-/, 1)[0]?.trim(), link: currentUrl, item: items, allowEmpty: true, diff --git a/lib/routes/chinaratings/credit-research.ts b/lib/routes/chinaratings/credit-research.ts index 7baab932be09..fae25399a999 100644 --- a/lib/routes/chinaratings/credit-research.ts +++ b/lib/routes/chinaratings/credit-research.ts @@ -11,7 +11,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx: Context): Promise => { const { category = 'Industry/Comment' } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '15', 10); + const limit = Number(ctx.req.query('limit') ?? '15'); const baseUrl = 'https://www.chinaratings.com.cn'; const targetUrl: string = new URL(`CreditResearch/${category.endsWith('/') ? category : `${category}/`}`, baseUrl).href; @@ -59,7 +59,7 @@ export const handler = async (ctx: Context): Promise => { const metaStr: string = $$('div.newshead p span, div.title p span').text(); const pubDateStr: string | undefined = metaStr?.match(/(\d{4}-\d{2}-\d{2})/)?.[1]; - const authors: DataItem['author'] = metaStr?.match(/来源:(.*?)/)?.[1]; + const authors: DataItem['author'] = metaStr?.match(/来源:(.*)/)?.[1]; const upDatedStr: string | undefined = pubDateStr; let processedItem: DataItem = { diff --git a/lib/routes/chinathinktanks/viewpoint.ts b/lib/routes/chinathinktanks/viewpoint.ts index 021c8654ef90..48d069e20632 100644 --- a/lib/routes/chinathinktanks/viewpoint.ts +++ b/lib/routes/chinathinktanks/viewpoint.ts @@ -105,7 +105,7 @@ async function handler(ctx) { const $ = load(response.data); const content = $('#art'); item.description = content.html(); - item.pubDate = item.pubDate.includes('-') ? timezone(parseDate(item.pubDate, 'YYYY-MM-DD'), +8) : parseRelativeDate(item.pubDate); + item.pubDate = item.pubDate.includes('-') ? timezone(parseDate(item.pubDate, 'YYYY-MM-DD'), 8) : parseRelativeDate(item.pubDate); return item; }) @@ -113,7 +113,7 @@ async function handler(ctx) { ); return { - title: `中国智库网 —— ${$('title').text().split('_中国智库网')[0]}`, + title: `中国智库网 —— ${$('title').text().split('_中国智库网', 1)[0]}`, link, item: items, }; diff --git a/lib/routes/chinatimes/index.ts b/lib/routes/chinatimes/index.ts index bfeb05a87d93..db4305191c6a 100644 --- a/lib/routes/chinatimes/index.ts +++ b/lib/routes/chinatimes/index.ts @@ -43,11 +43,11 @@ async function handler(ctx) { const response = await ofetch(link); const $ = load(response); - const browser = await playwright(); + const context = await playwright(); const list = $('.articlebox-compact') .toArray() - .slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20) + .slice(0, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20) .map((item) => { const $item = $(item); const a = $item.find('.title a'); @@ -66,10 +66,10 @@ async function handler(ctx) { const items = await Promise.all( list.map((item) => cache.tryGet(item.link, async () => { - const page = await browser.newPage(); - await page.setRequestInterception(true); - page.on('request', (request) => { - request.resourceType() === 'document' ? request.continue() : request.abort(); + const page = await context.newPage(); + await page.route('**/*', (route) => { + const request = route.request(); + request.resourceType() === 'document' ? route.continue() : route.abort(); }); logger.http(`Requesting ${item.link}`); await page.goto(item.link, { @@ -98,7 +98,7 @@ async function handler(ctx) { ) ); - await browser.close(); + await context.close(); return { title: $('head title').text(), diff --git a/lib/routes/chinaventure/index.ts b/lib/routes/chinaventure/index.ts index aff83521686f..88e84c418e06 100644 --- a/lib/routes/chinaventure/index.ts +++ b/lib/routes/chinaventure/index.ts @@ -57,13 +57,14 @@ async function handler(ctx) { url: currentUrl, }); const $ = load(response.data); + const limit = ctx.req.query('limit'); const list = $('a', '.common_newslist_pc') .filter((element) => $(element).attr('href')) .toArray() .map((item) => ({ link: rootUrl + $(item).attr('href'), })) - .slice(0, ctx.req.query('limit') ? Math.min(Number.parseInt(ctx.req.query('limit')), 20) : 20); + .slice(0, limit ? Number.parseInt(limit) : 20); const items = await Promise.all( list.map((item) => @@ -76,7 +77,7 @@ async function handler(ctx) { item.title = content('h1.maintitle_pc').text(); item.description = content('div.article_slice_pc').html(); item.author = content('div.source_author').text(); - item.pubDate = timezone(parseDate(content('div.releaseTime').text()), +8); + item.pubDate = timezone(parseDate(content('div.releaseTime').text()), 8); return item; }) ) diff --git a/lib/routes/chinawriter/index.ts b/lib/routes/chinawriter/index.ts index 946c8bc79fb1..87c61b8ecfb0 100644 --- a/lib/routes/chinawriter/index.ts +++ b/lib/routes/chinawriter/index.ts @@ -15,7 +15,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) : 40; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 40; const rootUrl = 'http://www.chinawriter.com.cn'; const currentUrl = `${new URL(id ?? '', rootUrl).href}/`; @@ -71,7 +71,7 @@ async function handler(ctx) { ].filter(Boolean) ), ]; - item.pubDate = content('div.end_info em').text() ? timezone(parseDate(content('div.end_info em').text(), 'YYYY年MM月DD日HH:mm'), +8) : parseDate(content('meta[name="publishdate"]').prop('content')); + item.pubDate = content('div.end_info em').text() ? timezone(parseDate(content('div.end_info em').text(), 'YYYY年MM月DD日HH:mm'), 8) : parseDate(content('meta[name="publishdate"]').prop('content')); } catch { // } diff --git a/lib/routes/chiphell/portal.ts b/lib/routes/chiphell/portal.ts index 1ff83542b7d0..752de923a457 100644 --- a/lib/routes/chiphell/portal.ts +++ b/lib/routes/chiphell/portal.ts @@ -24,7 +24,7 @@ const handler = async (ctx: Context) => { title: a.text(), link: `https://www.chiphell.com/${a.attr('href')}`, category: [$item.find('dd label').text()], - pubDate: timezone(parseDate($item.find('span.xg1').text()), +8), + pubDate: timezone(parseDate($item.find('span.xg1').text()), 8), }; }); diff --git a/lib/routes/chlinlearn/daily-blog.ts b/lib/routes/chlinlearn/daily-blog.ts index e0ef1a3aab42..a5a35e1119b8 100644 --- a/lib/routes/chlinlearn/daily-blog.ts +++ b/lib/routes/chlinlearn/daily-blog.ts @@ -42,7 +42,7 @@ export const route: Route = { link: item.url, author: item.author, img: item.icon, - pubDate: timezone(parseDate(item.publishTime), +8), + pubDate: timezone(parseDate(item.publishTime), 8), })); return { // 源标题 diff --git a/lib/routes/chnmus/exhibition.tsx b/lib/routes/chnmus/exhibition.tsx new file mode 100644 index 000000000000..8fae9bb87598 --- /dev/null +++ b/lib/routes/chnmus/exhibition.tsx @@ -0,0 +1,210 @@ +import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; + +import type { DataItem, Route } from '@/types'; +import cache from '@/utils/cache'; +import got from '@/utils/got'; +import { parseDate } from '@/utils/parse-date'; + +import { namespace } from './namespace'; + +// format the date to YYYY-MM-DD and handle missing year or month +const extractDates = (durationStr: string) => { + let startDate: string | undefined; + let endDate: string | undefined; + + if (!durationStr) { + return { startDate, endDate }; + } + + const parts = durationStr.split(/——|[-—~]/).map((p) => p.trim()); // currently ——and- is used, add — or ~ for redundency + const startStr = parts[0]; + const endStr = parts[1]; + + let startYear: string | undefined; + let startMonth: string | undefined; + + const startRegex = /(\d{4})年(\d{1,2})月(\d{1,2})日/; + const startMatch = startStr.match(startRegex); + + if (startMatch) { + startYear = startMatch[1]; + startMonth = startMatch[2].padStart(2, '0'); + const startDay = startMatch[3].padStart(2, '0'); + startDate = `${startYear}-${startMonth}-${startDay}`; + } + + if (endStr && startDate) { + const endRegex = /(?:(\d{4})年)?(?:(\d{1,2})月)?(\d{1,2})日/; + const endMatch = endStr.match(endRegex); + + if (endMatch) { + const matchYear = endMatch[1]; + const matchMonth = endMatch[2]?.padStart(2, '0'); + const matchDay = endMatch[3].padStart(2, '0'); + + const finalEndYear = matchYear || startYear; + const finalEndMonth = matchMonth || startMonth; + const finalEndDay = matchDay; + endDate = `${finalEndYear}-${finalEndMonth}-${finalEndDay}`; + } + } + + return { startDate, endDate }; +}; + +export const route: Route = { + path: '/information/exhibition/:type?', + categories: ['travel'], + example: '/chnmus/information/exhibition/special', + parameters: { + type: 'Exhibition type, supported values: special(特展详情). Default: All.', + }, + name: 'Special Exhibitions', + maintainers: ['magazian'], + radar: [ + { + source: ['www.chnmus.net/ch/information/exhibition/index.html'], + target: '/information/exhibition', + }, + ], + + handler: async (ctx) => { + const type = ctx.req.param('type'); + const isSpecial = type === 'special'; + + const baseUrl = 'https://www.chnmus.net'; + const apiUrl = `${baseUrl}/ch/information/exhibition/index.html`; + const museumName = namespace.zh?.name || namespace.name; + + const response = await got({ + method: 'get', + url: apiUrl, + }); + + const $ = load(response.data); + + const list = $('.col-md-6.d-flex.fadeInBottom') + .toArray() + .map((item) => { + const $item = $(item); + const link = $item.attr('href'); + const imgUrlRaw = $item.find('.lazyload').attr('data-bg'); + const listTitle = $item.find('.common-component-box-title').text(); + + return { + title: listTitle, + itemLink: `https:${link}`, + imgUrl: `https:${imgUrlRaw}`, + }; + }); + + const items = await Promise.all( + list.map((item) => { + // use seperate cache key for special path + const cacheKey = isSpecial ? `${item.itemLink}-special` : item.itemLink; + + return cache.tryGet(cacheKey, async (): Promise> => { + const detailResponse = await got({ + method: 'get', + url: item.itemLink, + }); + const content = load(detailResponse.data); + + const pubDateRaw = content('.common-component-content-attribute-item') + .toArray() + .map((el) => content(el).text()) + .find((text) => text.includes('发布日期'))! + .replaceAll('发布日期:', '') + .trim(); + const pubDate = parseDate(pubDateRaw); + + // Default path: return as news, no detail information for return + if (!isSpecial) { + return { + title: item.title, + link: item.itemLink, + pubDate, + description: renderToString( +
+ +
+ ), + } as Record; + } + + // Special path to return detail exhibition information + const texts = content('.common-component-content-text p') + .toArray() + .map((el) => content(el).text()); + + const title = texts.find((text) => text.includes('展览名称:'))?.replaceAll('展览名称:', ''); + + // filter out items without title, for example: https://www.chnmus.net/ch/information/exhibition/details.html?id=7400076083917230080#list + if (!title) { + return {} as Record; + } + + const location = texts + .find((text) => text.includes('展览地点:'))! + .replaceAll('展览地点:', '') + .trim(); + + const fullDuration = texts + .find((text) => text.includes('展览时间:') || text.includes('开展时间:')) + ?.replaceAll(/(展览|开展)时间:/g, '') + ?.trim(); + + const { startDate, endDate } = extractDates(fullDuration || ''); + const { imgUrl, itemLink } = item; + + const description = renderToString( +
+ +
+

+ 地点: + {location} +

+

+ 开展: + {startDate ?? '未定/常设'} +

+

+ 闭展: + {endDate ?? '未定/常设'} +

+ {fullDuration && ( +

+ 原始展期:{fullDuration} +

+ )} +
+ ); + + return { + title, + link: itemLink, + pubDate, + description, + _extra: { + museumName, + title, + location, + startDate, + endDate, + itemLink, + }, + } as Record; + }) as Promise; + }) + ); + + return { + title: `${museumName} - 展览资讯${isSpecial ? ' - 特展详情' : ''}`, + link: apiUrl, + language: 'zh-CN', + item: items.filter((item) => item.title) as DataItem[], + }; + }, +}; diff --git a/lib/routes/chnmus/namespace.ts b/lib/routes/chnmus/namespace.ts new file mode 100644 index 000000000000..e5fe10c6228f --- /dev/null +++ b/lib/routes/chnmus/namespace.ts @@ -0,0 +1,10 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'Henan Museum', + url: 'www.chnmus.net', + + zh: { + name: '河南博物院', + }, +}; diff --git a/lib/routes/chnmuseum/xingnew.ts b/lib/routes/chnmuseum/xingnew.ts index afccbdacb0ab..85716e00450c 100644 --- a/lib/routes/chnmuseum/xingnew.ts +++ b/lib/routes/chnmuseum/xingnew.ts @@ -9,7 +9,7 @@ import timezone from '@/utils/timezone'; export const route: Route = { path: '/zx/xingnew', categories: ['travel'], - example: '/zx/xingnew', + example: '/chnmuseum/zx/xingnew', parameters: {}, features: { requireConfig: false, @@ -41,7 +41,7 @@ export const route: Route = { return { title: a.attr('title') || a.text(), link: new URL(a.attr('href'), 'https://www.chnmuseum.cn/zx/xingnew/').href, - pubDate: timezone(parseDate(dateText, 'YYYY/MM/DD'), +8), + pubDate: timezone(parseDate(dateText, 'YYYY/MM/DD'), 8), // description: a.attr('title') || a.text(), }; }); diff --git a/lib/routes/chnmuseum/xwzt.ts b/lib/routes/chnmuseum/xwzt.ts index 719d91e62cfb..1953957b3cd6 100644 --- a/lib/routes/chnmuseum/xwzt.ts +++ b/lib/routes/chnmuseum/xwzt.ts @@ -6,7 +6,7 @@ import ofetch from '@/utils/ofetch'; export const route: Route = { path: '/zx/xwzt', categories: ['travel'], - example: '/zx/xwzt', + example: '/chnmuseum/zx/xwzt', parameters: {}, features: { requireConfig: false, diff --git a/lib/routes/chnmuseum/zl.tsx b/lib/routes/chnmuseum/zl.tsx index 43f86b39e31c..65e832285061 100644 --- a/lib/routes/chnmuseum/zl.tsx +++ b/lib/routes/chnmuseum/zl.tsx @@ -34,8 +34,7 @@ const formatExhibitionDate = (dateStr: string | undefined): string | undefined = const normalized = dateStr .replaceAll(/年|月/g, '-') .replaceAll('日', '') - .replaceAll('/', '-') - .replaceAll('.', '-'); + .replaceAll(/[/.]/g, '-'); const parts = normalized.split('-').filter(Boolean); if (parts.length === 3) { return `${parts[0]}-${parts[1].padStart(2, '0')}-${parts[2].padStart(2, '0')}`; @@ -87,12 +86,12 @@ const parseExhibitionDuration = (duration: string) => { }; // to identify the route config and titletag based on type and subtype, this function is used in both route handler and radar to ensure consistency -const resolveRouteConfig = (type: string | undefined, subType: string | undefined, baseUrl: string) => { +const resolveRouteConfig = (type: string | undefined, subtype: string | undefined, baseUrl: string) => { let url = `${baseUrl}/zl/`; let cleanType = ''; if (type) { - cleanType = subType ? `${type}/${subType}` : type; + cleanType = subtype ? `${type}/${subtype}` : type; url = `${baseUrl}/zl/${cleanType}/`; } return { @@ -103,7 +102,7 @@ const resolveRouteConfig = (type: string | undefined, subType: string | undefine }; // to concurrent or single-page retrieval according to titletag -const fetchTargetElements = async (cleanType: string, subType: string | undefined, url: string, baseUrl: string) => { +const fetchTargetElements = async (cleanType: string, subtype: string | undefined, url: string, baseUrl: string) => { const items: Array<{ $item: any; contextUrl: string }> = []; // Use a Set to track visited links and filter out duplicate HTML elements directly at the source // (e.g., when the same exhibition appears in both the main list and a specific sub-category list). @@ -169,12 +168,12 @@ export const route: Route = { ], handler: async (ctx: Context): Promise => { const type = ctx.req.param('type')?.trim(); - const subType = ctx.req.param('subType')?.trim(); + const subtype = ctx.req.param('subType')?.trim(); const museumName = namespace.zh?.name || namespace.name; const baseUrl = 'https://www.chnmuseum.cn'; - const { cleanType, url, titleTag } = resolveRouteConfig(type, subType, baseUrl); - const itemsToParse = await fetchTargetElements(cleanType, subType, url, baseUrl); + const { cleanType, url, titleTag } = resolveRouteConfig(type, subtype, baseUrl); + const itemsToParse = await fetchTargetElements(cleanType, subtype, url, baseUrl); const list = ( await Promise.all( diff --git a/lib/routes/chuanliu/nice.tsx b/lib/routes/chuanliu/nice.tsx index ecd758138885..cd609e4bb187 100644 --- a/lib/routes/chuanliu/nice.tsx +++ b/lib/routes/chuanliu/nice.tsx @@ -36,7 +36,7 @@ export const route: Route = { }; async function handler(ctx) { - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 100; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 100; const rootUrl = 'https://chuanliu.org'; const apiRootUrl = 'https://s.chuanliu.org'; diff --git a/lib/routes/cib/whpj.ts b/lib/routes/cib/whpj.ts index ec3637e60655..4f5335070f13 100644 --- a/lib/routes/cib/whpj.ts +++ b/lib/routes/cib/whpj.ts @@ -44,11 +44,11 @@ async function handler(ctx) { }); const response = await got('https://personalbank.cib.com.cn/pers/main/pubinfo/ifxQuotationQuery.do', { agent: { https: agent } }); - const cookies = response.headers['set-cookie'].map((item) => item.split(';')[0]).join(';'); + const cookies = response.headers['set-cookie'].map((item) => item.split(';', 1)[0]).join(';'); const $ = load(response.data); let date = $('div.main-body').find('div.labe_text').text(); - date = date.split('\n\t')[1].replace('日期:', '').trim(); + date = date.split('\n\t', 2)[1].replace('日期:', '').trim(); date = date.slice(0, 11) + date.slice(15); const link = 'https://personalbank.cib.com.cn/pers/main/pubinfo/ifxQuotationQuery/list?_search=false&dataSet.rows=80&dataSet.page=1&dataSet.sidx=&dataSet.sord=asc'; diff --git a/lib/routes/cih-index/report.ts b/lib/routes/cih-index/report.ts index b3f6a0263a49..b1008c181cb6 100644 --- a/lib/routes/cih-index/report.ts +++ b/lib/routes/cih-index/report.ts @@ -40,7 +40,7 @@ async function handler(ctx) { const initialState = JSON.parse( $('script:contains("window.__INITIAL_STATE__")') .text() - .match(/window\.__INITIAL_STATE__\s*=\s*({.*?});/)?.[1] || '{}' + .match(/window\.__INITIAL_STATE__\s*=\s*(\{.*?\});/)?.[1] || '{}' ); const { dataResult, indNavLists, secondNameFilter, tagList, param } = initialState.data; @@ -53,7 +53,7 @@ async function handler(ctx) { return { title: info.reportTitle, link: `${baseUrl}/report/detail/${info.reportId}.html`, - pubDate: timezone(parseDate(info.addTime), +8), + pubDate: timezone(parseDate(info.addTime), 8), category: [...new Set([...info.reportClassTagDtoList.map((t) => t.tag), ...(info.keywordList || [])])], description: `${info.context}
${images}`, image: `${info.coverFigureUrl}/200`, diff --git a/lib/routes/ciidbnu/index.ts b/lib/routes/ciidbnu/index.ts index f9411e84a647..92605a984a40 100644 --- a/lib/routes/ciidbnu/index.ts +++ b/lib/routes/ciidbnu/index.ts @@ -62,7 +62,7 @@ async function handler(ctx) { const content = load(iconv.decode(detailResponse.data, 'gbk')); item.description = content('#text').html(); - item.pubDate = timezone(parseDate(content('.t8').eq(0).text(), 'YYYY/M/D H:mm:ss'), +8); + item.pubDate = timezone(parseDate(content('.t8').eq(0).text(), 'YYYY/M/D H:mm:ss'), 8); content('.t14').remove(); diff --git a/lib/routes/cisia/index.ts b/lib/routes/cisia/index.ts index c1e5832b935f..0c5492072d18 100644 --- a/lib/routes/cisia/index.ts +++ b/lib/routes/cisia/index.ts @@ -7,7 +7,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx) => { const { id = '9' } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const domain = 'www.cisia.org'; const rootUrl = `http://${domain}`; @@ -35,7 +35,7 @@ export const handler = async (ctx) => { items = await Promise.all( items.map((item) => cache.tryGet(item.link, async () => { - if (!/^https?:\/\/www\.cisia\.org(\/[^\s]*)?$/.test(item.link)) { + if (!/^https?:\/\/www\.cisia\.org(?:\/\S*)?$/.test(item.link)) { return item; } diff --git a/lib/routes/civitai/models.ts b/lib/routes/civitai/models.ts index d1a75ada0eea..b6735c193fc5 100644 --- a/lib/routes/civitai/models.ts +++ b/lib/routes/civitai/models.ts @@ -38,7 +38,7 @@ async function handler() { const items = data.items.map((item) => ({ title: item.name, link: `https://civitai.com/models/${item.id}`, - description: `${item.modelVersions?.[0]?.images?.map((image) => ``).join('\n')}${item.description}`, + description: `${item.modelVersions?.[0]?.images?.map((image) => ``).join('\n')}${item.description}`, pubDate: parseDate(item.lastVersionAt), author: item.creator?.username, category: item.tags, diff --git a/lib/routes/ciweimao/chapter.ts b/lib/routes/ciweimao/chapter.ts index 10412edba6d0..0faa2d8ce9d8 100644 --- a/lib/routes/ciweimao/chapter.ts +++ b/lib/routes/ciweimao/chapter.ts @@ -53,7 +53,7 @@ async function handler(ctx) { return { chapterLocked: item.find('h3 i.icon-lock').length > 0, title: item.find('h3').text(), - pubDate: timezone(parseDate(item.find('p').text().replace('发布于 ', '')), +8), + pubDate: timezone(parseDate(item.find('p').text().replace('发布于 ', '')), 8), link: item.attr('href'), }; }); diff --git a/lib/routes/cjlu/yjsy/index.ts b/lib/routes/cjlu/yjsy/index.ts index 68804d6e1930..1d06a3d55586 100644 --- a/lib/routes/cjlu/yjsy/index.ts +++ b/lib/routes/cjlu/yjsy/index.ts @@ -83,22 +83,21 @@ export const route: Route = { async function handler(ctx) { const cate = ctx.req.param('cate'); - 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 url = `${host}index/${cate}.htm`; - const { page, destroy, browser } = await getPlaywrightPage(url, { + const { page, destroy } = await getPlaywrightPage(url, { onBeforeLoad: async (page) => { await page.setExtraHTTPHeaders(headers); - await page.setUserAgent(headers['User-Agent']); - await page.setRequestInterception(true); - page.on('request', (request) => { - allowedResourceTypes.has(request.resourceType()) ? request.continue() : request.abort(); + await page.route('**/*', (route) => { + const request = route.request(); + allowedResourceTypes.has(request.resourceType()) ? route.continue() : route.abort(); }); }, - gotoConfig: { waitUntil: 'networkidle2' }, + gotoConfig: { waitUntil: 'networkidle' }, }); - const cookies = await browser.cookies(); + const cookies = await page.context().cookies(); const cookieString = cookies.map((c) => `${c.name}=${c.value}`).join('; '); const response = await page.content(); @@ -121,7 +120,7 @@ async function handler(ctx) { return { title: a.attr('title') ?? titleMap.get(cate) ?? '中量大研究生院通知', - pubDate: timezone(parseDate(timeStr, 'YYYY/MM/DD'), +8), + pubDate: timezone(parseDate(timeStr, 'YYYY/MM/DD'), 8), link: `${host}${route}`, description: '', }; diff --git a/lib/routes/claude/blog.ts b/lib/routes/claude/blog.ts index 9cc243c6baea..50b2828eed45 100644 --- a/lib/routes/claude/blog.ts +++ b/lib/routes/claude/blog.ts @@ -37,7 +37,7 @@ async function handler(ctx) { const link = `${baseUrl}/blog`; const response = await ofetch(link); const $ = load(response); - 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 list: DataItem[] = $('.blog_cms_list article.card_blog_list_wrap') .toArray() diff --git a/lib/routes/claude/code-changelog.ts b/lib/routes/claude/code-changelog.ts index 7d8483231530..167f71e7212a 100644 --- a/lib/routes/claude/code-changelog.ts +++ b/lib/routes/claude/code-changelog.ts @@ -7,7 +7,7 @@ import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; const handler = async (ctx: Context): Promise => { - const limit = Number.parseInt(ctx.req.query('limit') ?? '25', 10); + const limit = Number(ctx.req.query('limit') ?? '25'); const baseUrl = 'https://code.claude.com'; const targetUrl = `${baseUrl}/docs/en/changelog`; diff --git a/lib/routes/cline/blog.ts b/lib/routes/cline/blog.ts index 7455759c8df2..4ae95ce50cea 100644 --- a/lib/routes/cline/blog.ts +++ b/lib/routes/cline/blog.ts @@ -21,7 +21,7 @@ function extractArticlesFromDOM($: CheerioAPI): DataItem[] { // Extract date and author with single regex const metaText = element.find('.text-sm.text-slate-500').text().trim(); - const metaMatch = metaText.match(/^([^•]+)\s*•\s*([A-Za-z]+\s+\d{1,2},?\s+\d{4})/); + const metaMatch = metaText.match(/^([^•]+)•\s*([A-Z]+\s+\d{1,2},?\s+\d{4})/i); const author = metaMatch ? metaMatch[1].trim() : 'Cline Team'; const pubDate = metaMatch ? parseDate(metaMatch[2]) : undefined; diff --git a/lib/routes/cloudflarestatus/index.tsx b/lib/routes/cloudflarestatus/index.tsx index 12d2ff4c228e..74444264f585 100644 --- a/lib/routes/cloudflarestatus/index.tsx +++ b/lib/routes/cloudflarestatus/index.tsx @@ -11,7 +11,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') ?? '100', 10); + const limit = Number(ctx.req.query('limit') ?? '100'); const baseUrl = 'https://www.cloudflarestatus.com'; @@ -40,7 +40,7 @@ export const handler = async (ctx: Context): Promise => { ); const pubDateStr: string | undefined = $el.find('span.ago').attr('data-datetime-unix'); - const linkUrl: string | undefined = $actualTitleEl.attr('href') ? new URL($actualTitleEl.attr('href') as string, baseUrl).toString() : undefined; + const linkUrl: string | undefined = $actualTitleEl.attr('href') ? new URL($actualTitleEl.attr('href') as string, baseUrl).href : undefined; const categories: string[] = [type].filter(Boolean); const guid: string = linkUrl ? `${linkUrl}#${pubDateStr}` : ''; const upDatedStr: string | undefined = pubDateStr; diff --git a/lib/routes/cloudnative/blog.ts b/lib/routes/cloudnative/blog.ts index 4da530cc9c69..c286a0445f7f 100644 --- a/lib/routes/cloudnative/blog.ts +++ b/lib/routes/cloudnative/blog.ts @@ -19,7 +19,7 @@ async function getArticles() { title: a.text(), link: a.attr('href'), description: summary.text(), - pubDate: timezone(parseDate(time, 'YYYY-MM-DD'), +8), + pubDate: timezone(parseDate(time, 'YYYY-MM-DD'), 8), author: meta.find('span').eq(0).find('a').text(), category: meta.find('.article-categories a').text(), }; diff --git a/lib/routes/cls/subject.ts b/lib/routes/cls/subject.ts index 9b6e07d42f1f..9216bdfe4fc0 100644 --- a/lib/routes/cls/subject.ts +++ b/lib/routes/cls/subject.ts @@ -10,7 +10,7 @@ import { getSearchParams, rootUrl } from './utils'; export const handler = async (ctx) => { const { id = '1103' } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const currentUrl = new URL(`subject/${id}`, rootUrl).href; const apiUrl = new URL(`api/subject/${id}/article`, rootUrl).href; diff --git a/lib/routes/cls/utils.ts b/lib/routes/cls/utils.ts index 4dd78a58d9bb..701fedcca649 100644 --- a/lib/routes/cls/utils.ts +++ b/lib/routes/cls/utils.ts @@ -12,7 +12,8 @@ const getSearchParams = (moreParams?) => { const filtered = Object.fromEntries(Object.entries({ ...params, ...moreParams }).filter(([_, v]) => v !== undefined)); const searchParams = new URLSearchParams(filtered as Record); searchParams.sort(); - searchParams.append('sign', CryptoJS.MD5(CryptoJS.SHA1(searchParams.toString()).toString()).toString()); + const sha1 = CryptoJS.SHA1(searchParams.toString()).toString(); + searchParams.append('sign', CryptoJS.MD5(sha1).toString()); return Object.fromEntries(searchParams); }; diff --git a/lib/routes/cma/channel.tsx b/lib/routes/cma/channel.tsx index 7eb1fd9673b1..abdcf0c96f0b 100644 --- a/lib/routes/cma/channel.tsx +++ b/lib/routes/cma/channel.tsx @@ -110,7 +110,7 @@ async function handler(ctx) { ?.split(/:/) ?.pop() || author, guid: `cma${data.link}#${data.releaseTime.replaceAll(/\s/g, '-')}`, - pubDate: timezone(parseDate(data.releaseTime), +8), + pubDate: timezone(parseDate(data.releaseTime), 8), enclosure_url: new URL(data.image, rootUrl).href, enclosure_type: data.image ? `image/${data.image.split(/\./).pop()}` : undefined, }, diff --git a/lib/routes/cmde/index.ts b/lib/routes/cmde/index.ts index 0f7776e45237..910b5b16bd7d 100644 --- a/lib/routes/cmde/index.ts +++ b/lib/routes/cmde/index.ts @@ -18,12 +18,12 @@ export const route: Route = { async function handler(ctx) { const cate = ctx.req.param('cate') ?? 'xwdt/zxyw'; const url = `${rootURL}/${cate}/`; - const browser = await playwright(); + const context = await playwright(); const data = await cache.tryGet(url, 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(url, { waitUntil: 'domcontentloaded', @@ -52,10 +52,10 @@ async function handler(ctx) { const items = await Promise.all( data.items.map((item) => cache.tryGet(item.link, 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(item.link, { waitUntil: 'domcontentloaded', @@ -66,13 +66,13 @@ async function handler(ctx) { await page.close(); const $ = load(html); item.description = $('.text').html(); - item.pubDate = timezone(parseDate($('meta[name="PubDate"]').attr('content')), +8); + item.pubDate = timezone(parseDate($('meta[name="PubDate"]').attr('content')), 8); return item; }) ) ); - await browser.close(); + await context.close(); return { title: data.title, diff --git a/lib/routes/cmpxchg8b/articles.ts b/lib/routes/cmpxchg8b/articles.ts index 63a883b2a363..55d1fac15dc5 100644 --- a/lib/routes/cmpxchg8b/articles.ts +++ b/lib/routes/cmpxchg8b/articles.ts @@ -42,7 +42,7 @@ async function handler() { item = $(item); return { title: item.find('li').text(), - link: new URL(item.find('li a').attr('href'), baseUrl).toString(), + link: new URL(item.find('li a').attr('href'), baseUrl).href, author, }; }); @@ -64,7 +64,7 @@ async function handler() { return { title, - link: new URL('#articles', baseUrl).toString(), + link: new URL('#articles', baseUrl).href, item: items, }; } diff --git a/lib/routes/cna/index.ts b/lib/routes/cna/index.ts index 65f2b41f5845..4b27805ae3f1 100644 --- a/lib/routes/cna/index.ts +++ b/lib/routes/cna/index.ts @@ -30,7 +30,7 @@ export const route: Route = { async function handler(ctx) { const id = ctx.req.param('id') || 'aall'; const isTopic = /^\d+$/.test(id); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; // The API used by the website when hitting "看更多內容" const { data: response } = await got({ @@ -52,7 +52,7 @@ async function handler(ctx) { const list = (isTopic ? resultData.Topic.NewsItems : resultData.Items).slice(0, limit).map((item) => ({ title: item.HeadLine, link: item.PageUrl, - pubDate: timezone(parseDate(item.CreateTime), +8), + pubDate: timezone(parseDate(item.CreateTime), 8), })); const items = await Promise.all(list.map((item) => cache.tryGet(item.link, async () => await getFullText(item)))); diff --git a/lib/routes/cna/web/index.ts b/lib/routes/cna/web/index.ts index b56bd7921eee..3c29a6fae3b0 100644 --- a/lib/routes/cna/web/index.ts +++ b/lib/routes/cna/web/index.ts @@ -44,7 +44,7 @@ async function handler(ctx) { return { title: item.text(), link: new URL(item.parents('a').attr('href'), 'https://www.cna.com.tw').href, - pubDate: timezone(parseDate(item.next().text()), +8), + pubDate: timezone(parseDate(item.next().text()), 8), }; }); diff --git a/lib/routes/cnbeta/category.ts b/lib/routes/cnbeta/category.ts index f2a916204809..d7f53286cce2 100644 --- a/lib/routes/cnbeta/category.ts +++ b/lib/routes/cnbeta/category.ts @@ -4,7 +4,7 @@ import { handler } from './common'; export const route: Route = { name: '分类', - path: ['/category/:id'], + path: '/category/:id', example: '/cnbeta/category/movie', maintainers: ['nczitzk'], parameters: { diff --git a/lib/routes/cnbeta/common.ts b/lib/routes/cnbeta/common.ts index a96f3a49b5c8..20267dd71608 100644 --- a/lib/routes/cnbeta/common.ts +++ b/lib/routes/cnbeta/common.ts @@ -1,6 +1,5 @@ import { load } from 'cheerio'; -import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; @@ -25,8 +24,8 @@ export async function handler(ctx) { ? response.data.result.list.map((item) => ({ title: item.title, description: item.hometext, - author: item.source.split('@http')[0], - pubDate: timezone(parseDate(item.inputtime), +8), + author: item.source.split('@http', 1)[0], + pubDate: timezone(parseDate(item.inputtime), 8), link: item.url_show.startsWith('//') ? `https:${item.url_show}` : item.url_show.replace('http:', 'https:'), category: item.label.name, })) @@ -39,6 +38,6 @@ export async function handler(ctx) { return { title: $('title').text(), link: currentUrl, - item: await ProcessItems(items, ctx.req.query('limit'), cache.tryGet), + item: await ProcessItems(items, ctx.req.query('limit')), }; } diff --git a/lib/routes/cnbeta/index.ts b/lib/routes/cnbeta/index.ts index 2d1d8d94f0ed..1900b9e2e09a 100644 --- a/lib/routes/cnbeta/index.ts +++ b/lib/routes/cnbeta/index.ts @@ -4,7 +4,7 @@ import { handler } from './common'; export const route: Route = { name: '头条资讯', - path: ['/'], + path: '/', example: '/cnbeta', radar: [ { diff --git a/lib/routes/cnbeta/topics.ts b/lib/routes/cnbeta/topics.ts index a2d986130d02..5aa977c03470 100644 --- a/lib/routes/cnbeta/topics.ts +++ b/lib/routes/cnbeta/topics.ts @@ -4,7 +4,7 @@ import { handler } from './common'; export const route: Route = { name: '主题', - path: ['/topics/:id'], + path: '/topics/:id', example: '/cnbeta/topics/453', maintainers: ['cczhong11', 'nczitzk'], parameters: { diff --git a/lib/routes/cnbeta/utils.ts b/lib/routes/cnbeta/utils.ts index a7df94b7de05..66d349eefc77 100644 --- a/lib/routes/cnbeta/utils.ts +++ b/lib/routes/cnbeta/utils.ts @@ -1,15 +1,16 @@ import { load } from 'cheerio'; +import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; const rootUrl = 'https://www.cnbeta.com.tw'; -const ProcessItems = (items, limit, tryGet) => +const ProcessItems = (items, limit) => Promise.all( items.slice(0, limit ? Number.parseInt(limit) : 60).map((item) => - tryGet(item.link, async () => { + cache.tryGet(item.link, async () => { const detailResponse = await got(item.link); const content = load(detailResponse.data); @@ -18,7 +19,7 @@ const ProcessItems = (items, limit, tryGet) => item.description = content('.article-summary').html() + content('.article-content').html(); item.author = content('header.title div.meta span.source').text(); - item.pubDate ??= timezone(parseDate(content('.meta span').first().text(), 'YYYY年MM月DD日 HH:mm'), +8); + item.pubDate ??= timezone(parseDate(content('.meta span').first().text(), 'YYYY年MM月DD日 HH:mm'), 8); return item; }) diff --git a/lib/routes/cnblogs/common.ts b/lib/routes/cnblogs/common.ts index 3e7be267182d..98c2efe617f3 100644 --- a/lib/routes/cnblogs/common.ts +++ b/lib/routes/cnblogs/common.ts @@ -44,7 +44,7 @@ async function handler(ctx) { return { title: item.find('.post-item-title').text(), link: item.find('.post-item-title').attr('href'), - pubDate: timezone(parseDate(item.find('.post-item-foot .post-meta-item span').text() || item.find('.editorpick-item-meta').text(), ['YYYY-MM-DD HH:mm', 'YYYY-MM-DD']), +8), + pubDate: timezone(parseDate(item.find('.post-item-foot .post-meta-item span').text() || item.find('.editorpick-item-meta').text(), ['YYYY-MM-DD HH:mm', 'YYYY-MM-DD']), 8), description: item.find('.post-item-summary').text(), author: item.find('.post-item-author span').text(), }; diff --git a/lib/routes/cncf/index.ts b/lib/routes/cncf/index.ts index a4d07888f806..b3e48a90a757 100644 --- a/lib/routes/cncf/index.ts +++ b/lib/routes/cncf/index.ts @@ -40,7 +40,7 @@ async function handler(ctx) { .map((item) => ({ title: $(item).find('span.post-archive__title').text().trim(), link: $(item).find('span.post-archive__title > a').attr('href'), - pubDate: parseDate($(item).find('span.post-archive__item_date').text().split('|')[0]), + pubDate: parseDate($(item).find('span.post-archive__item_date').text().split('|', 1)[0]), })); const items = await Promise.all( diff --git a/lib/routes/cncf/reports.ts b/lib/routes/cncf/reports.ts index 16785af22506..52400646c384 100644 --- a/lib/routes/cncf/reports.ts +++ b/lib/routes/cncf/reports.ts @@ -38,7 +38,7 @@ async function handler() { const detailResponse = await got(item.link); const content = load(detailResponse.data); - item.parseDate = parseDate(content('p.is-style-spaced-uppercase').splice(':')[1]); + item.parseDate = parseDate(content('p.is-style-spaced-uppercase').text().split(':', 2)[1]); item.description = content('article > div.has-background').html(); return item; diff --git a/lib/routes/cneb/yjxw.ts b/lib/routes/cneb/yjxw.ts index 3ec000203d2f..341b484623b5 100644 --- a/lib/routes/cneb/yjxw.ts +++ b/lib/routes/cneb/yjxw.ts @@ -71,7 +71,7 @@ async function handler(ctx) { return { title: a.text(), link: a.attr('href'), - pubDate: timezone(parseDate(item.find('span').text()), +8), + pubDate: timezone(parseDate(item.find('span').text()), 8), }; }); diff --git a/lib/routes/cneb/yjxx.ts b/lib/routes/cneb/yjxx.ts index 23ea3ee59fa5..5a56b9e57516 100644 --- a/lib/routes/cneb/yjxx.ts +++ b/lib/routes/cneb/yjxx.ts @@ -54,7 +54,7 @@ async function handler(ctx) { link: item.docpuburl, author: item.chnlname, description: item.doccontent, - pubDate: timezone(parseDate(item.docpubtime), +8), + pubDate: timezone(parseDate(item.docpubtime), 8), })); return { diff --git a/lib/routes/cngal/entry.tsx b/lib/routes/cngal/entry.tsx index a9edfe536393..60dca9e99735 100644 --- a/lib/routes/cngal/entry.tsx +++ b/lib/routes/cngal/entry.tsx @@ -42,7 +42,7 @@ async function handler(ctx) { item: data.newsOfEntry.map((item) => ({ title: item.title, description: renderDescription(item), - pubDate: timezone(parseDate(item.happenedTime), +8), + pubDate: timezone(parseDate(item.happenedTime), 8), link: item.link, })), }; diff --git a/lib/routes/cngold/index.ts b/lib/routes/cngold/index.ts index 400c25e2d611..f9d546a39b5f 100644 --- a/lib/routes/cngold/index.ts +++ b/lib/routes/cngold/index.ts @@ -7,7 +7,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx) => { const { category = 'news-325' } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 12; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 12; const rootUrl = 'https://www.cngold.org.cn'; const currentUrl = new URL(`${category}.html`, rootUrl).href; diff --git a/lib/routes/cnki/debut.ts b/lib/routes/cnki/debut.ts index a269a73fdbf3..d855b28a7d72 100644 --- a/lib/routes/cnki/debut.ts +++ b/lib/routes/cnki/debut.ts @@ -36,7 +36,8 @@ async function handler(ctx) { const name = ctx.req.param('name'); const journalUrl = `${rootUrl}/knavi/JournalDetail?pcode=CjFD&pykm=${name}`; - const title = await got.get(journalUrl).then((res) => load(res.data)('head > title').text()); + const res = await got.get(journalUrl); + const title = load(res.data)('head > title').text(); const outlineUrl = `${rootUrl}/knavi/JournalDetail/GetnfAllOutline`; const response = await got({ diff --git a/lib/routes/cnki/journals.ts b/lib/routes/cnki/journals.ts index 24cd3b80d73c..ec101d614eef 100644 --- a/lib/routes/cnki/journals.ts +++ b/lib/routes/cnki/journals.ts @@ -64,16 +64,15 @@ async function handler(ctx) { } const journalUrl = `${rootUrl}/knavi/journals/${name}/detail`; - const title = await got.get(journalUrl).then((res) => load(res.data)('head > title').text()); + const titleRes = await got.get(journalUrl); + const title = load(titleRes.data)('head > title').text(); const yearListUrl = `${rootUrl}/knavi/journals/${name}/yearList?pIdx=0`; - const { code, date } = await got.get(yearListUrl).then((res) => { - const $ = load(res.data); - const code = $('.yearissuepage').find('dl').first().find('dd').find('a').first().attr('value'); - const date = parseDate($('.yearissuepage').find('dl').first().find('dd').find('a').first().attr('id').replace('yq', ''), 'YYYYMM'); - return { code, date }; - }); + const yearListRes = await got.get(yearListUrl); + const $yearList = load(yearListRes.data); + const code = $yearList('.yearissuepage').find('dl').first().find('dd').find('a').first().attr('value'); + const date = parseDate($yearList('.yearissuepage').find('dl').first().find('dd').find('a').first().attr('id').replace('yq', ''), 'YYYYMM'); const yearIssueUrl = `${rootUrl}/knavi/journals/${name}/papers?yearIssue=${code}&pageIdx=0&pcode=CJFD,CCJD`; const response = await got.post(yearIssueUrl); diff --git a/lib/routes/cnljxh/index.ts b/lib/routes/cnljxh/index.ts index a78a42d0df14..0af711fc0cc2 100644 --- a/lib/routes/cnljxh/index.ts +++ b/lib/routes/cnljxh/index.ts @@ -11,7 +11,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx: Context): Promise => { const { category = 'news', id = '10' } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '20', 10); + const limit = Number(ctx.req.query('limit') ?? '20'); const baseUrl = 'https://www.cnljxh.org.cn'; const targetUrl: string = new URL(`${category}/?classid=${id}`, baseUrl).href; @@ -55,7 +55,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('div.content_title h2').text(); const description: string | undefined = $$('div.content_div').html() ?? ''; - const authors: DataItem['author'] = $$('div.content_title p').text().split(/\s/)[0]?.split(/:/).pop(); + const authors: DataItem['author'] = $$('div.content_title p').text().split(/\s/, 1)[0]?.split(/:/).pop(); let processedItem: DataItem = { title, diff --git a/lib/routes/cockroachlabs/blog.ts b/lib/routes/cockroachlabs/blog.ts index a5ef5cc2394a..f23a108eea52 100644 --- a/lib/routes/cockroachlabs/blog.ts +++ b/lib/routes/cockroachlabs/blog.ts @@ -88,7 +88,7 @@ async function handler(ctx) { try { const date = new Date(dateText); if (!Number.isNaN(date.getTime())) { - pubDate = parseDate(date.toISOString().split('T')[0]); + pubDate = parseDate(date.toISOString().split('T', 1)[0]); } } catch { // Ignore parsing errors diff --git a/lib/routes/codefather/posts.ts b/lib/routes/codefather/posts.ts index 84412c0dbce2..9953e2dae37a 100644 --- a/lib/routes/codefather/posts.ts +++ b/lib/routes/codefather/posts.ts @@ -88,7 +88,7 @@ async function handler(ctx) { } return { - title: content.split('\n')[0] || '无标题', + title: content.split('\n', 1)[0] || '无标题', link: `https://www.codefather.cn/post/${item.id}`, description, pubDate: parseDate(item.createTime as number), diff --git a/lib/routes/cohere/index.ts b/lib/routes/cohere/index.ts index b0c52eef3d14..56cd6aa56bb0 100644 --- a/lib/routes/cohere/index.ts +++ b/lib/routes/cohere/index.ts @@ -3,7 +3,7 @@ import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; export const route: Route = { - path: ['/blog'], + path: '/blog', name: 'Blog', url: 'cohere.com/blog', maintainers: ['Loongphy'], diff --git a/lib/routes/coindesk/utils.ts b/lib/routes/coindesk/utils.ts index a8ed6705a47b..c5124704cfcc 100644 --- a/lib/routes/coindesk/utils.ts +++ b/lib/routes/coindesk/utils.ts @@ -10,7 +10,7 @@ export const parseItem = async (item) => { $('.article-ad, #strategy-rules-player-wrapper, [data-module-name="newsletter-article-sign-up-module"], div.flex.flex-col.gap-2').remove(); const cover = $('.article-content-wrapper figure'); - cover.find('img').attr('src', cover.find('img').attr('url')?.split('?')[0]); + cover.find('img').attr('src', cover.find('img').attr('url')?.split('?', 1)[0]); cover.find('img').removeAttr('style srcset url'); item.description = @@ -21,7 +21,7 @@ export const parseItem = async (item) => { .join(''); item.pubDate = parseDate(ldJson.datePublished); item.author = ldJson.author.map((a) => ({ name: a.name })); - item.image = ldJson.image.url.split('?')[0]; + item.image = ldJson.image.url.split('?', 1)[0]; return item; }; diff --git a/lib/routes/cointelegraph/index.ts b/lib/routes/cointelegraph/index.ts index e984e6d7f64c..33bb1d2d1e71 100644 --- a/lib/routes/cointelegraph/index.ts +++ b/lib/routes/cointelegraph/index.ts @@ -41,7 +41,7 @@ async function handler(): Promise { .filter((item) => item.link && /\/news|\/explained|\/innovation-circle/.test(item.link)) .map((item) => ({ ...item, - link: item.link?.split('?')[0], + link: item.link?.split('?', 1)[0], })) .map((item) => cache.tryGet(item.link!, async () => { diff --git a/lib/routes/colamanga/manga.ts b/lib/routes/colamanga/manga.ts index 626f1b0d8067..bda4c200b721 100644 --- a/lib/routes/colamanga/manga.ts +++ b/lib/routes/colamanga/manga.ts @@ -43,14 +43,13 @@ async function handler(ctx: Context) { const id = ctx.req.param('id'); const url = `https://${domain}/${id}`; - const browser = await playwright(); + const context = await playwright(); - const page = await browser.newPage(); + const page = await context.newPage(); - 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(); }); logger.http(`Requesting ${url}`); @@ -60,7 +59,7 @@ async function handler(ctx: Context) { }); const response = await page.content(); - await browser.close(); + await context.close(); const $ = load(response); diff --git a/lib/routes/comicat/search.ts b/lib/routes/comicat/search.ts index 46d191f7aa86..6051a5916418 100644 --- a/lib/routes/comicat/search.ts +++ b/lib/routes/comicat/search.ts @@ -44,8 +44,8 @@ async function handler(ctx) { const { data: response } = await got(item.link); const $ = load(response); - item.pubDate = parseDate($('div.main > div.slayout > div > div.c1 > div:nth-child(1) > div > p:nth-child(4)').text().split('发布时间: ')[1]); - const marginLink = `magnet:?xt=urn:btih:${$('#text_hash_id').text().split(',特征码:')[1]}`.trim(); + item.pubDate = parseDate($('div.main > div.slayout > div > div.c1 > div:nth-child(1) > div > p:nth-child(4)').text().split('发布时间: ', 2)[1]); + const marginLink = `magnet:?xt=urn:btih:${$('#text_hash_id').text().split(',特征码:', 2)[1]}`.trim(); item.enclosure_url = marginLink; item.enclosure_type = 'application/x-bittorrent'; item.description = $('#btm > div.main > div.slayout > div > div.c2 > div:nth-child(1) > div.intro').html(); diff --git a/lib/routes/cool18/index.ts b/lib/routes/cool18/index.ts index f9d9da1b1453..189fe1aa4077 100644 --- a/lib/routes/cool18/index.ts +++ b/lib/routes/cool18/index.ts @@ -65,7 +65,7 @@ function buildUrl(rootUrl: string, type: PostType, keyword: string | undefined, function extractHomeList($: CheerioAPI, rootUrl: string, limit: number): DataItem[] { try { const scriptText = $('script:contains("_PageData")').text(); - const match = scriptText.match(/const\s+_PageData\s*=\s*(\[[\s\S]*?]);/); + const match = scriptText.match(/const\s+_PageData\s*=\s*(\[[\s\S]*?\]);/); if (!match?.[1]) { return []; @@ -225,7 +225,7 @@ async function handler(ctx: Context) { const $ = load(response); const limitQuery = ctx.req.query('limit'); - const limit = limitQuery ? Number.parseInt(limitQuery as string, 10) : 20; + const limit = limitQuery ? Number(limitQuery as string) : 20; let list: DataItem[]; diff --git a/lib/routes/coolapk/hot.ts b/lib/routes/coolapk/hot.ts index 6aec2286542c..2899e09eec32 100644 --- a/lib/routes/coolapk/hot.ts +++ b/lib/routes/coolapk/hot.ts @@ -5,7 +5,6 @@ import utils from './utils'; const getLinkAndTitle = (type, period) => { const baseURL = 'https://api.coolapk.com/v6/page/dataList?url='; - let link; const res = {}; const types = { jrrm: { @@ -32,6 +31,12 @@ const getLinkAndTitle = (type, period) => { }, }; + if (type === 'jrrm') { + res.link = types.jrrm.url; + res.title = types.jrrm.title; + return res; + } + let link; const periods = { daily: { description: '日榜', @@ -42,12 +47,7 @@ const getLinkAndTitle = (type, period) => { statType: '7days', }, }; - - if (type === 'jrrm') { - res.link = types.jrrm.url; - res.title = types.jrrm.title; - return res; - } else if (type === 'ktb') { + if (type === 'ktb') { const trans = { daily: { description: '周榜', diff --git a/lib/routes/coolapk/tuwen.ts b/lib/routes/coolapk/tuwen.ts index 4b5b7130c93d..927affaee486 100644 --- a/lib/routes/coolapk/tuwen.ts +++ b/lib/routes/coolapk/tuwen.ts @@ -4,7 +4,7 @@ import got from '@/utils/got'; import utils from './utils'; export const route: Route = { - path: ['/tuwen/:type?'], + path: '/tuwen/:type?', categories: ['social-media'], example: '/coolapk/tuwen', parameters: { type: '默认为hot' }, diff --git a/lib/routes/coolbuy/index.tsx b/lib/routes/coolbuy/index.tsx index 0c33ba8ad7a6..694bcff4403f 100644 --- a/lib/routes/coolbuy/index.tsx +++ b/lib/routes/coolbuy/index.tsx @@ -8,7 +8,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') ?? '50', 10); + const limit = Number(ctx.req.query('limit') ?? '50'); const baseUrl = 'https://coolbuy.com'; const imageBaseUrl = 'https://mcache.ifanr.cn'; @@ -29,8 +29,8 @@ export const handler = async (ctx: Context): Promise => { const items: DataItem[] = response.objects.slice(0, limit).map((item): DataItem => { const title: string = item.title; - const image: string | undefined = item.cover_image?.split(/\?/)?.[0]; - const banner: string | undefined = item.display_image?.split(/\?/)?.[0]; + const image: string | undefined = item.cover_image?.split(/\?/, 1)?.[0]; + const banner: string | undefined = item.display_image?.split(/\?/, 1)?.[0]; const images = [banner, image].filter(Boolean).map((image) => ({ src: image, diff --git a/lib/routes/coomer/index.tsx b/lib/routes/coomer/index.tsx index cdd0cd8d4c85..81fc21ac9e9e 100644 --- a/lib/routes/coomer/index.tsx +++ b/lib/routes/coomer/index.tsx @@ -103,7 +103,8 @@ async function handler(ctx) { } }); desc = (coomerFiles.length > 0 ? coomerFiles[0] : '') + $.html(); - for (const coomerFile of coomerFiles.slice(count + 1)) { + const remainingCoomerFiles = coomerFiles.slice(count + 1); + for (const coomerFile of remainingCoomerFiles) { desc += coomerFile; } @@ -122,7 +123,7 @@ async function handler(ctx) { } enclosureInfo = { - enclosure_url: new URL(src, rootUrl).toString(), + enclosure_url: new URL(src, rootUrl).href, enclosure_type: mimeType, }; }); diff --git a/lib/routes/cosplaytele/article.ts b/lib/routes/cosplaytele/article.ts index 2089557dc371..a6d370843584 100644 --- a/lib/routes/cosplaytele/article.ts +++ b/lib/routes/cosplaytele/article.ts @@ -3,7 +3,7 @@ import { load } from 'cheerio'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -async function loadArticle(link) { +export const loadArticle = async (link) => { const resp = await got(link); const article = load(resp.body); @@ -17,6 +17,4 @@ async function loadArticle(link) { pubDate, link, }; -} - -export default loadArticle; +}; diff --git a/lib/routes/cosplaytele/category.ts b/lib/routes/cosplaytele/category.ts index 0111e614c8e4..e18aa7705566 100644 --- a/lib/routes/cosplaytele/category.ts +++ b/lib/routes/cosplaytele/category.ts @@ -4,7 +4,7 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import loadArticle from './article'; +import { loadArticle } from './article'; import { SUB_NAME_PREFIX, SUB_URL } from './const'; export const route: Route = { diff --git a/lib/routes/cosplaytele/latest.ts b/lib/routes/cosplaytele/latest.ts index 50ccbb11a7de..38ab47d80310 100644 --- a/lib/routes/cosplaytele/latest.ts +++ b/lib/routes/cosplaytele/latest.ts @@ -4,7 +4,7 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import loadArticle from './article'; +import { loadArticle } from './article'; import { SUB_NAME_PREFIX, SUB_URL } from './const'; export const route: Route = { diff --git a/lib/routes/cosplaytele/popular.ts b/lib/routes/cosplaytele/popular.ts index 82b9333bf42c..279af76ce202 100644 --- a/lib/routes/cosplaytele/popular.ts +++ b/lib/routes/cosplaytele/popular.ts @@ -4,7 +4,7 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import loadArticle from './article'; +import { loadArticle } from './article'; import { SUB_NAME_PREFIX, SUB_URL } from './const'; export const route: Route = { diff --git a/lib/routes/cosplaytele/tag.ts b/lib/routes/cosplaytele/tag.ts index db3639c03232..c77583a6c817 100644 --- a/lib/routes/cosplaytele/tag.ts +++ b/lib/routes/cosplaytele/tag.ts @@ -4,7 +4,7 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import loadArticle from './article'; +import { loadArticle } from './article'; import { SUB_NAME_PREFIX, SUB_URL } from './const'; export const route: Route = { diff --git a/lib/routes/costar/press-releases.ts b/lib/routes/costar/press-releases.ts index 579c1afa2580..2f192d07a287 100644 --- a/lib/routes/costar/press-releases.ts +++ b/lib/routes/costar/press-releases.ts @@ -11,7 +11,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx: Context): Promise => { const { filter } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '10', 10); + const limit = Number(ctx.req.query('limit') ?? '10'); const baseUrl = 'https://www.costar.com'; const targetUrl: string = new URL(`products/benchmark/resources/press-releases${filter ? `?${filter}` : ''}`, baseUrl).href; diff --git a/lib/routes/counter-strike/news.ts b/lib/routes/counter-strike/news.ts index 0053926a607b..5999283224ba 100644 --- a/lib/routes/counter-strike/news.ts +++ b/lib/routes/counter-strike/news.ts @@ -53,7 +53,7 @@ const customPreset: PresetFactory = presetHTML5.extend((tags) => ({ export const handler = async (ctx) => { const { category = 'all', language = 'english' } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 100; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 100; const rootUrl = 'https://www.counter-strike.net'; const apiRootUrl = 'https://store.steampowered.com'; diff --git a/lib/routes/cpcaauto/index.ts b/lib/routes/cpcaauto/index.ts index 770dcc41d580..824611ddcfdf 100644 --- a/lib/routes/cpcaauto/index.ts +++ b/lib/routes/cpcaauto/index.ts @@ -8,7 +8,7 @@ import timezone from '@/utils/timezone'; export const handler = async (ctx) => { const { type = 'news', id } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const rootUrl = 'http://cpcaauto.com'; const currentUrl = new URL(`news.php${type ? `?types=${type}${id ? `&anid=${id}` : ''}` : ''}`, rootUrl).href; @@ -44,7 +44,7 @@ export const handler = async (ctx) => { item.title = title; item.description = description; - item.pubDate = timezone(parseDate($$('div.view span').first().text().split(/:/).pop()), +8); + item.pubDate = timezone(parseDate($$('div.view span').first().text().split(/:/).pop()), 8); item.content = { html: description, text: $$('div.text').text(), diff --git a/lib/routes/creative-comic/book.tsx b/lib/routes/creative-comic/book.tsx index 8951f809c71f..ccf80547876f 100644 --- a/lib/routes/creative-comic/book.tsx +++ b/lib/routes/creative-comic/book.tsx @@ -32,7 +32,7 @@ export const route: Route = { async function handler(ctx) { const { id, coverOnly = 'true', quality = '1' } = ctx.req.param(); - const uuid = await getUuid(cache.tryGet); + const uuid = await getUuid(); const { data: { data: book }, } = await getBook(id, uuid); @@ -40,10 +40,12 @@ async function handler(ctx) { data: { data: chapters }, } = await getChapters(id, uuid); + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 3; + const items = await Promise.all( chapters.chapters .toSorted((a, b) => b.idx - a.idx) - .slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 3) + .slice(0, limit) .map(async (c) => { let pages; if (coverOnly !== 'true' && coverOnly !== '1') { diff --git a/lib/routes/creative-comic/utils.ts b/lib/routes/creative-comic/utils.ts index 115136b8901b..cc1bdef32144 100644 --- a/lib/routes/creative-comic/utils.ts +++ b/lib/routes/creative-comic/utils.ts @@ -1,5 +1,6 @@ import CryptoJS from 'crypto-js'; +import cache from '@/utils/cache'; import got from '@/utils/got'; const apiHost = 'https://api.creative-comic.tw'; @@ -48,8 +49,8 @@ const getImgKey = (pageId, uuid) => }, }); -const getUuid = (tryGet) => - tryGet('creative-comic:uuid', async () => { +const getUuid = () => + cache.tryGet('creative-comic:uuid', async () => { const { data } = await got(`${apiHost}/guest`, { headers: { device, diff --git a/lib/routes/cryptoslate/index.ts b/lib/routes/cryptoslate/index.ts index abebfd6e0c93..77c4725f6587 100644 --- a/lib/routes/cryptoslate/index.ts +++ b/lib/routes/cryptoslate/index.ts @@ -46,7 +46,7 @@ async function handler(ctx): Promise { try { // Clean URL by removing query parameters - const cleanUrl = item.link.split('?')[0]; + const cleanUrl = item.link.split('?', 1)[0]; return { title: item.title || 'Untitled', diff --git a/lib/routes/cs/index.ts b/lib/routes/cs/index.ts index c9fb5dccd9cd..85b4e998dd39 100644 --- a/lib/routes/cs/index.ts +++ b/lib/routes/cs/index.ts @@ -72,7 +72,7 @@ export const route: Route = { async function handler(ctx) { const { category = 'xwzx' } = 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.cs.com.cn'; const currentUrl = new URL(category.endsWith('/') ? category : `${category}/`, rootUrl).href; @@ -92,7 +92,7 @@ async function handler(ctx) { return { title: item.find('h3').text().trim(), link: new URL(item.prop('href'), currentUrl).href, - pubDate: timezone(parseDate(item.find('em').text()), +8), + pubDate: timezone(parseDate(item.find('em').text()), 8), }; }); @@ -113,7 +113,7 @@ async function handler(ctx) { .slice(1) .toArray() .map((c) => content(c).prop('title') ?? content(c).text()); - item.pubDate = timezone(parseDate(content('.time').prop('datetime')), +8); + item.pubDate = timezone(parseDate(content('.time').prop('datetime')), 8); } catch { // no-empty } diff --git a/lib/routes/cs/video.ts b/lib/routes/cs/video.ts index 9669cd574d6c..3475e455102b 100644 --- a/lib/routes/cs/video.ts +++ b/lib/routes/cs/video.ts @@ -27,7 +27,7 @@ export const route: Route = { async function handler(ctx) { const { category = '今日聚焦' } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 50; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 50; const rootUrl = 'https://video.cs.com.cn'; const apiCategoryUrl = new URL('web/api/getCategory', rootUrl).href; @@ -64,8 +64,8 @@ async function handler(ctx) { link: new URL(item.contentUrl, rootUrl).href, description: item.contentDetails, author: item.contentSource, - pubDate: timezone(parseDate(item.contentDatetime), +8), - updated: timezone(parseDate(item.updateDate), +8), + pubDate: timezone(parseDate(item.contentDatetime), 8), + updated: timezone(parseDate(item.updateDate), 8), })); const { data: currentResponse } = await got(currentUrl); diff --git a/lib/routes/ctinews/topic.ts b/lib/routes/ctinews/topic.ts index ec1d4b745feb..9560bcacbfb1 100644 --- a/lib/routes/ctinews/topic.ts +++ b/lib/routes/ctinews/topic.ts @@ -88,7 +88,7 @@ async function handler(ctx) { const $ = load(response); if (item.link?.includes('/videos/')) { const ldJson = JSON.parse($('script[type="application/ld+json"]:contains("VideoObject")').text()); - const videoId = ldJson.embedUrl.match(/embed\/([a-zA-Z0-9_-]+)/)?.[1]; + const videoId = ldJson.embedUrl.match(/embed\/([\w-]+)/)?.[1]; item.description = `
` + diff --git a/lib/routes/cts/news.ts b/lib/routes/cts/news.ts index ce35dba6525b..e7222482d8a2 100644 --- a/lib/routes/cts/news.ts +++ b/lib/routes/cts/news.ts @@ -37,8 +37,9 @@ async function handler(ctx) { const currentUrl = `https://news.cts.com.tw/${category}/index.html`; const response = await got(currentUrl); const $ = load(response.data); + const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 20; const items = await pMap( - $('#newslist-top a[title]').slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 20), + $('#newslist-top a[title]').slice(0, limit), (item) => { item = $(item); const link = item.attr('href'); diff --git a/lib/routes/cuilingmag/index.ts b/lib/routes/cuilingmag/index.ts index 47ba39a7fc08..e2e612f9503b 100644 --- a/lib/routes/cuilingmag/index.ts +++ b/lib/routes/cuilingmag/index.ts @@ -9,7 +9,7 @@ import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const { category } = ctx.req.param(); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 12; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 12; const rootUrl = 'https://www.cuilingmag.com'; const currentUrl = new URL(category ? `category/${category}` : '', rootUrl).href; diff --git a/lib/routes/cursor/blog.ts b/lib/routes/cursor/blog.ts index 11e7014b2cf3..d73e83fe0d39 100644 --- a/lib/routes/cursor/blog.ts +++ b/lib/routes/cursor/blog.ts @@ -8,7 +8,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx: Context): Promise => { const { topic, locale } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '10', 10); + const limit = Number(ctx.req.query('limit') ?? '10'); const baseUrl = 'https://cursor.com'; const normalizedTopic = topic === 'all' ? undefined : topic; diff --git a/lib/routes/cursor/changelog.ts b/lib/routes/cursor/changelog.ts index fc7aadbbdddf..a3965126ba24 100644 --- a/lib/routes/cursor/changelog.ts +++ b/lib/routes/cursor/changelog.ts @@ -10,7 +10,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx: Context): Promise => { const locale = ctx.req.param('locale'); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '100', 10); + const limit = Number(ctx.req.query('limit') ?? '100'); const baseUrl = 'https://cursor.com'; const localeSegment = locale ? `/${locale}` : ''; diff --git a/lib/routes/cw/author.ts b/lib/routes/cw/author.ts index 2be67a126dfe..fda1501a0996 100644 --- a/lib/routes/cw/author.ts +++ b/lib/routes/cw/author.ts @@ -27,11 +27,11 @@ export const route: Route = { }; async function handler(ctx) { - const browser = await playwright(); + const context = await playwright(); - const { $, items } = await parsePage('author', browser, ctx); + const { $, items } = await parsePage('author', context, ctx); - await browser.close(); + await context.close(); return { title: $('head title').text(), diff --git a/lib/routes/cw/master.ts b/lib/routes/cw/master.ts index eaae855f3f21..239d6c22a334 100644 --- a/lib/routes/cw/master.ts +++ b/lib/routes/cw/master.ts @@ -37,11 +37,11 @@ export const route: Route = { }; async function handler(ctx) { - const browser = await playwright(); + const context = await playwright(); - const { $, items } = await parsePage('master', browser, ctx); + const { $, items } = await parsePage('master', context, ctx); - await browser.close(); + await context.close(); return { title: $('head title').text(), diff --git a/lib/routes/cw/sub.ts b/lib/routes/cw/sub.ts index 3f5abdb728bb..a374c6d1921c 100644 --- a/lib/routes/cw/sub.ts +++ b/lib/routes/cw/sub.ts @@ -22,11 +22,11 @@ export const route: Route = { }; async function handler(ctx) { - const browser = await playwright(); + const context = await playwright(); - const { $, items } = await parsePage('sub', browser, ctx); + const { $, items } = await parsePage('sub', context, ctx); - await browser.close(); + await context.close(); return { title: $('head title').text(), diff --git a/lib/routes/cw/today.ts b/lib/routes/cw/today.ts index dd34056332ab..73e4303f4c0e 100644 --- a/lib/routes/cw/today.ts +++ b/lib/routes/cw/today.ts @@ -28,11 +28,11 @@ export const route: Route = { }; async function handler(ctx) { - const browser = await playwright(); + const context = await playwright(); - const { $, items } = await parsePage('today', browser, ctx); + const { $, items } = await parsePage('today', context, ctx); - await browser.close(); + await context.close(); return { title: $('head title').text(), diff --git a/lib/routes/cw/utils.ts b/lib/routes/cw/utils.ts index f1f9a9859505..514064e458ee 100644 --- a/lib/routes/cw/utils.ts +++ b/lib/routes/cw/utils.ts @@ -1,5 +1,6 @@ import { load } from 'cheerio'; +import { config } from '@/config'; import cache from '@/utils/cache'; import logger from '@/utils/logger'; import ofetch from '@/utils/ofetch'; @@ -29,13 +30,13 @@ const pathMap = { }, }; -const getCookie = async (browser, tryGet) => { +const getCookie = async (context) => { if (!cookie) { - cookie = await tryGet('cw:cookie', async () => { - const page = await browser.newPage(); - await page.setRequestInterception(true); - page.on('request', (request) => { - request.resourceType() === 'document' || request.resourceType() === 'script' ? request.continue() : request.abort(); + cookie = await cache.tryGet('cw:cookie', async () => { + const page = await context.newPage(); + await page.route('**/*', (route) => { + const request = route.request(); + request.resourceType() === 'document' || request.resourceType() === 'script' ? route.continue() : route.abort(); }); logger.http(`Requesting ${baseUrl}/user/get/cookie-bar`); await page.goto(`${baseUrl}/user/get/cookie-bar`, { @@ -49,14 +50,14 @@ const getCookie = async (browser, tryGet) => { return cookie; }; -const parsePage = async (path, browser, ctx) => { +const parsePage = async (path, context, ctx) => { const pageUrl = `${baseUrl}${pathMap[path].pageUrl(ctx.req.param('channel'))}`; - const cookie = await getCookie(browser, cache.tryGet); - const page = await browser.newPage(); - await page.setRequestInterception(true); - page.on('request', (request) => { - request.resourceType() === 'document' || request.resourceType() === 'script' ? request.continue() : request.abort(); + const cookie = await getCookie(context); + const page = await context.newPage(); + await page.route('**/*', (route) => { + const request = route.request(); + request.resourceType() === 'document' || request.resourceType() === 'script' ? route.continue() : route.abort(); }); await setCookies(page, cookie, 'cw.com.tw'); logger.http(`Requesting ${pageUrl}`); @@ -70,7 +71,7 @@ const parsePage = async (path, browser, ctx) => { const $ = load(response); const list = parseList($, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : pathMap[path].limit); - const items = await parseItems(list, browser, cache.tryGet); + const items = await parseItems(list, context); return { $, items }; }; @@ -88,14 +89,14 @@ const parseList = ($, limit) => }) .slice(0, limit); -const parseItems = (list, browser, tryGet) => +const parseItems = (list, context) => Promise.all( list.map((item) => - tryGet(item.link, async () => { + cache.tryGet(item.link, async () => { const response = await ofetch(item.link, { headers: { - Cookie: await getCookie(browser, tryGet), - 'User-Agent': browser.userAgent(), + Cookie: await getCookie(context), + 'User-Agent': config.ua, }, }); const $ = load(response); @@ -103,10 +104,12 @@ const parseItems = (list, browser, tryGet) => const meta = JSON.parse($('head script[type="application/ld+json"]').eq(0).text()); $('.article__head .breadcrumb, .article__head h1, .article__provideViews, .ad').remove(); $('img.lazyload').each((_, img) => { - if (img.attribs['data-src']) { - img.attribs.src = img.attribs['data-src']; - delete img.attribs['data-src']; + if (!img.attribs['data-src']) { + return; } + + img.attribs.src = img.attribs['data-src']; + delete img.attribs['data-src']; }); item.title = $('head title').text(); diff --git a/lib/routes/cybersecurityventures/news.ts b/lib/routes/cybersecurityventures/news.ts index 29cb7e21858f..97936d5d57fe 100644 --- a/lib/routes/cybersecurityventures/news.ts +++ b/lib/routes/cybersecurityventures/news.ts @@ -59,18 +59,18 @@ export const route: Route = { categories: ['programming'], path: '/news/:category?', example: '/cybersecurityventures/news', - radar: Object.keys(categories).map((key) => ({ + radar: Object.entries(categories).map(([key, value]) => ({ source: [`cybersecurityventures.com/${key}`], target: `/news/${key}`, - title: categories[key].label, + title: value.label, })), parameters: { category: { description: 'news category', default: 'today', - options: Object.keys(categories).map((key) => ({ + options: Object.entries(categories).map(([key, value]) => ({ value: key, - label: categories[key].label, + label: value.label, })), }, }, @@ -88,7 +88,7 @@ async function handler(ctx: Context): Promise { const category = ctx.req.param('category') ?? 'today'; const limit = ctx.req.query('limit') ?? 20; - if (!(category in categories)) { + if (!Object.hasOwn(categories, category)) { throw new InvalidParameterError('Invalid category'); } diff --git a/lib/routes/cyzone/author.ts b/lib/routes/cyzone/author.ts index 995f3c128f3f..760055725758 100644 --- a/lib/routes/cyzone/author.ts +++ b/lib/routes/cyzone/author.ts @@ -1,5 +1,4 @@ import type { Route } from '@/types'; -import cache from '@/utils/cache'; import { apiRootUrl, getInfo, processItems, rootUrl } from './util'; @@ -28,17 +27,17 @@ 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) : 5; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 5; const apiUrl = new URL('v2/author/author/detail', apiRootUrl).href; const currentUrl = new URL(`author/${id}`, rootUrl).href; - const items = await processItems(apiUrl, limit, cache.tryGet, { + const items = await processItems(apiUrl, limit, { author_id: id, }); return { item: items, - ...(await getInfo(currentUrl, cache.tryGet)), + ...(await getInfo(currentUrl)), }; } diff --git a/lib/routes/cyzone/index.ts b/lib/routes/cyzone/index.ts index 5cde9e9751db..c359bdb18afd 100644 --- a/lib/routes/cyzone/index.ts +++ b/lib/routes/cyzone/index.ts @@ -1,5 +1,4 @@ import type { Route } from '@/types'; -import cache from '@/utils/cache'; import { apiRootUrl, getInfo, processItems, rootUrl } from './util'; @@ -29,7 +28,7 @@ export const route: Route = { async function handler(ctx) { const { id = 'news' } = ctx.req.param(); - 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 apiUrl = new URL(`v2/content/channel/${id === 'news' ? 'getArticle' : 'detail'}`, apiRootUrl).href; const currentUrl = new URL(`channel/${id}`, rootUrl).href; @@ -37,7 +36,6 @@ async function handler(ctx) { const items = await processItems( apiUrl, limit, - cache.tryGet, id === 'news' ? {} : { @@ -47,6 +45,6 @@ async function handler(ctx) { return { item: items, - ...(await getInfo(currentUrl, cache.tryGet)), + ...(await getInfo(currentUrl)), }; } diff --git a/lib/routes/cyzone/label.ts b/lib/routes/cyzone/label.ts index 47f45412058a..55fae5981856 100644 --- a/lib/routes/cyzone/label.ts +++ b/lib/routes/cyzone/label.ts @@ -1,5 +1,4 @@ import type { Route } from '@/types'; -import cache from '@/utils/cache'; import { apiRootUrl, getInfo, processItems, rootUrl } from './util'; @@ -28,17 +27,17 @@ export const route: Route = { async function handler(ctx) { const name = ctx.req.param('name'); - 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 apiUrl = new URL('v2/content/tag/tagList', apiRootUrl).href; const currentUrl = new URL(`label/${name}`, rootUrl).href; - const items = await processItems(apiUrl, limit, cache.tryGet, { + const items = await processItems(apiUrl, limit, { tag: name, }); return { item: items, - ...(await getInfo(currentUrl, cache.tryGet)), + ...(await getInfo(currentUrl)), }; } diff --git a/lib/routes/cyzone/util.ts b/lib/routes/cyzone/util.ts index 2f61635d0a83..e3c7ed6095a3 100644 --- a/lib/routes/cyzone/util.ts +++ b/lib/routes/cyzone/util.ts @@ -1,5 +1,6 @@ import { load } from 'cheerio'; +import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; @@ -8,18 +9,17 @@ const apiRootUrl = 'https://api1.cyzone.cn'; const apiShowUrl = new URL('v2/content/app_content/show', apiRootUrl).href; /** - * Retrieves information from a given URL using a provided tryGet function. + * Retrieves information from a given URL. * @param {string} url - The URL to retrieve information from. - * @param {Function} tryGet - The tryGet function that handles the retrieval process. * @returns {Promise} - A promise that resolves to an object containing the retrieved information. */ -const getInfo = (url, tryGet) => - tryGet(url, async () => { +const getInfo = (url) => + cache.tryGet(url, async () => { const { data: response } = await got(url); const $ = load(response); - const avatar = $('img.avatar')?.prop('src')?.split('?')[0] ?? undefined; + const avatar = $('img.avatar')?.prop('src')?.split('?', 1)[0] ?? undefined; const icon = new URL($('link[rel="icon"]')?.prop('href'), rootUrl).href; const image = new URL($('div.logo img')?.prop('src'), rootUrl).href; @@ -40,11 +40,10 @@ const getInfo = (url, tryGet) => * Process the item list and return the resulting array. * @param {string} apiUrl - The URL of the API. * @param {number} limit - The limit of the results. - * @param {function} tryGet - The tryGet function that handles the retrieval process. * @param {...Object} searchParams - The search parameter objects. * @returns {Promise} - The processed item array. */ -const processItems = async (apiUrl, limit, tryGet, ...params) => { +const processItems = async (apiUrl, limit, ...params) => { // Merge search parameters let searchParams = { size: limit, @@ -76,7 +75,7 @@ const processItems = async (apiUrl, limit, tryGet, ...params) => { items = await Promise.all( items.map((item) => - tryGet(`cyzone-${item.guid}`, async () => { + cache.tryGet(`cyzone-${item.guid}`, async () => { const { data: detailResponse } = await got.post(apiShowUrl, { json: { content_id: item.guid, @@ -91,7 +90,7 @@ const processItems = async (apiUrl, limit, tryGet, ...params) => { content('img').each((_, el) => { if (content(el).prop('src')) { - content(el).prop('src', content(el).prop('src').split('?')[0]); + content(el).prop('src', content(el).prop('src').split('?', 1)[0]); } else { content(el).remove(); } diff --git a/lib/routes/cztv/daily.tsx b/lib/routes/cztv/daily.tsx index 61c820a86df8..dc96588c6af0 100644 --- a/lib/routes/cztv/daily.tsx +++ b/lib/routes/cztv/daily.tsx @@ -57,7 +57,7 @@ async function handler() { return { title, link, - pubDate: timezone(parseDate(item.find('span.t2').text() + ' 16:30', 'YYYY-MM-DD hh:mm'), +8), + pubDate: timezone(parseDate(item.find('span.t2').text() + ' 16:30', 'YYYY-MM-DD hh:mm'), 8), }; }); diff --git a/lib/routes/cztv/zjxwlb.tsx b/lib/routes/cztv/zjxwlb.tsx index 989e46d2d3f3..b2a777b29c83 100644 --- a/lib/routes/cztv/zjxwlb.tsx +++ b/lib/routes/cztv/zjxwlb.tsx @@ -55,7 +55,7 @@ async function handler() { return { title, link, - pubDate: timezone(parseDate(item.find('span.t2').text() + ' 16:30', 'YYYY-MM-DD hh:mm'), +8), + pubDate: timezone(parseDate(item.find('span.t2').text() + ' 16:30', 'YYYY-MM-DD hh:mm'), 8), description: renderDesc({ title, videoUrl, posterUrl }), }; }); diff --git a/lib/routes/daily/discussed.ts b/lib/routes/daily/discussed.ts index 92973c8a7dae..6b012d0c88e9 100644 --- a/lib/routes/daily/discussed.ts +++ b/lib/routes/daily/discussed.ts @@ -93,10 +93,10 @@ export const route: Route = { }; async function handler(ctx) { - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const innerSharedContent = ctx.req.param('innerSharedContent') ? JSON.parse(ctx.req.param('innerSharedContent')) : false; const dateSort = ctx.req.param('dateSort') ? JSON.parse(ctx.req.param('dateSort')) : true; - const period = ctx.req.param('period') ? Number.parseInt(ctx.req.param('period'), 10) : 7; + const period = ctx.req.param('period') ? Number(ctx.req.param('period')) : 7; const link = `${baseUrl}/posts/discussed`; diff --git a/lib/routes/daily/popular.ts b/lib/routes/daily/popular.ts index 8360dc059315..8b48490beb64 100644 --- a/lib/routes/daily/popular.ts +++ b/lib/routes/daily/popular.ts @@ -144,7 +144,7 @@ export const route: Route = { async function handler(ctx) { const link = `${baseUrl}/posts`; - 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 innerSharedContent = ctx.req.param('innerSharedContent') ? JSON.parse(ctx.req.param('innerSharedContent')) : false; const dateSort = ctx.req.param('dateSort') ? JSON.parse(ctx.req.param('dateSort')) : true; diff --git a/lib/routes/daily/source.ts b/lib/routes/daily/source.ts index 0c9aba5a45e4..104b5fdebe64 100644 --- a/lib/routes/daily/source.ts +++ b/lib/routes/daily/source.ts @@ -147,7 +147,7 @@ export const route: Route = { async function handler(ctx) { const sourceId = ctx.req.param('sourceId'); - 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 innerSharedContent = ctx.req.param('innerSharedContent') ? JSON.parse(ctx.req.param('innerSharedContent')) : false; const link = `${baseUrl}/sources/${sourceId}`; diff --git a/lib/routes/daily/squads.ts b/lib/routes/daily/squads.ts index 5bc199acaa9e..c71880192536 100644 --- a/lib/routes/daily/squads.ts +++ b/lib/routes/daily/squads.ts @@ -213,7 +213,7 @@ export const route: Route = { }; async function handler(ctx) { - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const innerSharedContent = ctx.req.param('innerSharedContent') ? JSON.parse(ctx.req.param('innerSharedContent')) : false; const squads = ctx.req.param('squads'); diff --git a/lib/routes/daily/upvoted.ts b/lib/routes/daily/upvoted.ts index 06885d298dd2..43f9f27238e3 100644 --- a/lib/routes/daily/upvoted.ts +++ b/lib/routes/daily/upvoted.ts @@ -153,10 +153,10 @@ export const route: Route = { async function handler(ctx) { const link = `${baseUrl}/posts/upvoted`; - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; const innerSharedContent = ctx.req.param('innerSharedContent') ? JSON.parse(ctx.req.param('innerSharedContent')) : false; const dateSort = ctx.req.param('dateSort') ? JSON.parse(ctx.req.param('dateSort')) : true; - const period = ctx.req.param('period') ? Number.parseInt(ctx.req.param('period'), 10) : 7; + const period = ctx.req.param('period') ? Number(ctx.req.param('period')) : 7; const data = await getData({ query, diff --git a/lib/routes/daily/user.ts b/lib/routes/daily/user.ts index cbffaed740d5..d3eda7ba1698 100644 --- a/lib/routes/daily/user.ts +++ b/lib/routes/daily/user.ts @@ -160,7 +160,7 @@ export const route: Route = { async function handler(ctx) { const userId = ctx.req.param('userId'); - const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 7; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 7; const innerSharedContent = ctx.req.param('innerSharedContent') ? JSON.parse(ctx.req.param('innerSharedContent')) : false; const buildId = await getBuildId(); diff --git a/lib/routes/dailypush/all.ts b/lib/routes/dailypush/all.ts index 6a39fe133178..e92405045348 100644 --- a/lib/routes/dailypush/all.ts +++ b/lib/routes/dailypush/all.ts @@ -42,12 +42,12 @@ async function handler(ctx) { const { sort = '' } = ctx.req.param(); const url = sort ? `${BASE_URL}/${sort}` : BASE_URL; - const browser = await playwright(); + const context = await playwright(); try { - const html = await fetchPageHtml(browser, url, 'article'); + const html = await fetchPageHtml(context, url, 'article'); const $ = load(html); const list = parseArticles($, BASE_URL); - const items = await enhanceItemsWithSummaries(browser, list); + const items = await enhanceItemsWithSummaries(context, list); const pageTitle = $('title').text() || 'DailyPush - All'; @@ -57,6 +57,6 @@ async function handler(ctx) { item: items, }; } finally { - await browser.close(); + await context.close(); } } diff --git a/lib/routes/dailypush/tags.ts b/lib/routes/dailypush/tags.ts index c1430d29ae47..0cac69fc7345 100644 --- a/lib/routes/dailypush/tags.ts +++ b/lib/routes/dailypush/tags.ts @@ -43,12 +43,12 @@ async function handler(ctx) { const { tag, sort = 'trending' } = ctx.req.param(); const url = `${BASE_URL}/${tag}/${sort}`; - const browser = await playwright(); + const context = await playwright(); try { - const html = await fetchPageHtml(browser, url, 'article'); + const html = await fetchPageHtml(context, url, 'article'); const $ = load(html); const list = parseArticles($, BASE_URL); - const items = await enhanceItemsWithSummaries(browser, list); + const items = await enhanceItemsWithSummaries(context, list); const pageTitle = $('title').text() || `DailyPush - ${tag.charAt(0).toUpperCase() + tag.slice(1)}`; @@ -58,6 +58,6 @@ async function handler(ctx) { item: items, }; } finally { - await browser.close(); + await context.close(); } } diff --git a/lib/routes/dailypush/utils.ts b/lib/routes/dailypush/utils.ts index 1fd24ec3ad85..134eca52ff32 100644 --- a/lib/routes/dailypush/utils.ts +++ b/lib/routes/dailypush/utils.ts @@ -1,11 +1,12 @@ import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; +import type { BrowserContext } from 'patchright'; import type { DataItem } from '@/types'; import cache from '@/utils/cache'; import logger from '@/utils/logger'; import { parseRelativeDate } from '@/utils/parse-date'; -import type { Browser, Page } from '@/utils/playwright'; +import type { Page } from '@/utils/playwright'; export const BASE_URL = 'https://www.dailypush.dev'; @@ -23,19 +24,19 @@ export interface ArticleItem { const allowedRequestTypes = new Set(['document']); async function preparePage(page: Page) { - await page.setRequestInterception(true); - page.on('request', (request) => { + await page.route('**/*', (route) => { + const request = route.request(); if (allowedRequestTypes.has(request.resourceType())) { - request.continue(); + route.continue(); return; } - request.abort(); + route.abort(); }); } -export async function fetchPageHtml(browser: Browser, url: string, waitForSelector?: string): Promise { - const page = await browser.newPage(); +export async function fetchPageHtml(context: BrowserContext, url: string, waitForSelector?: string): Promise { + const page = await context.newPage(); await preparePage(page); try { @@ -143,7 +144,7 @@ function extractCategories(article: ReturnType, $: CheerioAPI): stri const tagText = tagElement.text().trim(); // Skip summary/stats links and navigation - if (tagHref && tagText && !tagHref.includes('article/') && !tagHref.includes('Summary') && tagText.length < 50 && !/^(Summary|stats|About|Tags|Toggle|Trending|Latest|Previous|Next)$/i.test(tagText)) { + if (tagHref && tagText && !tagHref.includes('article/') && !tagHref.includes('Summary') && tagText.length < 50 && !/^(?:Summary|stats|About|Tags|Toggle|Trending|Latest|Previous|Next)$/i.test(tagText)) { return tagText; } return null; @@ -259,9 +260,9 @@ export function parseArticles($: CheerioAPI, baseUrl: string): ArticleItem[] { /** * Enhance items with full summaries from dailypush article pages. - * Uses the provided browser; opens a new tab per URL (document requests only). Caller must close the browser. + * Uses the provided context; opens a new tab per URL (document requests only). Caller must close the context. */ -export async function enhanceItemsWithSummaries(browser: Browser, items: ArticleItem[]): Promise { +export async function enhanceItemsWithSummaries(context: BrowserContext, items: ArticleItem[]): Promise { const itemsWithUrl = items.filter((item) => item.dailyPushUrl !== undefined); const itemsWithoutUrl: DataItem[] = items.filter((item) => item.dailyPushUrl === undefined); @@ -269,7 +270,7 @@ export async function enhanceItemsWithSummaries(browser: Browser, items: Article itemsWithUrl.map((item) => cache.tryGet(item.dailyPushUrl!, async () => { try { - const html = await fetchPageHtml(browser, item.dailyPushUrl!, 'p.font-ibm-plex-sans.leading-relaxed'); + const html = await fetchPageHtml(context, item.dailyPushUrl!, 'p.font-ibm-plex-sans.leading-relaxed'); const $ = load(html); const summary = $('p.font-ibm-plex-sans.leading-relaxed'); if (summary.length > 0 && summary.text().trim()) { diff --git a/lib/routes/dangdang/notice.ts b/lib/routes/dangdang/notice.ts index 1de1063b21d6..e68baf115485 100644 --- a/lib/routes/dangdang/notice.ts +++ b/lib/routes/dangdang/notice.ts @@ -48,7 +48,7 @@ async function handler(ctx) { documentId: item.documentId, source: `https://open.dangdang.com/op-api/developer-platform/document/info/get?document_id=${item.documentId}`, link: `https://open.dangdang.com/home/notice/message/1/${item.documentId}`, - pubDate: timezone(parseDate(item.modifyTime), +8), + pubDate: timezone(parseDate(item.modifyTime), 8), })); const result = await Promise.all( diff --git a/lib/routes/dapenti/utils.ts b/lib/routes/dapenti/utils.ts index 0b69eefb135a..9cbc28a5f050 100644 --- a/lib/routes/dapenti/utils.ts +++ b/lib/routes/dapenti/utils.ts @@ -60,7 +60,7 @@ export default { title: el.text(), author: pubInfo[0].trim(), description: description.html(), - pubDate: timezone(parseDate(pubInfo[1]?.trim()), +8), + pubDate: timezone(parseDate(pubInfo[1]?.trim()), 8), link: url, }; return single; diff --git a/lib/routes/daum/potplayer.ts b/lib/routes/daum/potplayer.ts index 919afa2166b1..865d74b24ff0 100644 --- a/lib/routes/daum/potplayer.ts +++ b/lib/routes/daum/potplayer.ts @@ -7,7 +7,7 @@ import { parseDate } from '@/utils/parse-date'; export const handler = async (ctx: Context): Promise => { const { lang } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '500', 10); + const limit = Number(ctx.req.query('limit') ?? '500'); const baseUrl = 'https://t1.daumcdn.net'; const targetUrl: string = new URL(`potplayer/PotPlayer/v4/Update2/Update${lang ?? ''}.html`, baseUrl).href; @@ -20,14 +20,14 @@ export const handler = async (ctx: Context): Promise => { // Group 3: Trailing hyphens (unused, but for context) // Group 4: Update content // Uses global and multiline flags for all matches and line start/end anchors - const updateRegex = /^(-+)\s*\n(.*?)\s*\n(-+)\s*\n([\s\S]*?)(?=\n-{2,}|<\/p>)/gm; + const updateRegex = /^-+[^\S\n]*\n(.*)\r?\n-+[^\S\n]*\n([\s\S]*?)(?=\n-{2}|<\/p>)/gm; const items: DataItem[] = []; let match: RegExpExecArray | null; while ((match = updateRegex.exec(response)) !== null && items.length < limit) { - const headerLine: string | undefined = match[2].trim(); - const description: string | undefined = match[4].trim()?.replaceAll(/(\s[+-])/g, '
$1'); + const headerLine: string | undefined = match[1].trim(); + const description: string | undefined = match[2].trim()?.replaceAll(/(\s[+-])/g, '
$1'); let version = 'N/A'; let pubDateStr: string | undefined = undefined; @@ -48,9 +48,9 @@ export const handler = async (ctx: Context): Promise => { } else if (numericDateMatch && numericDateMatch[1]) { const rawDate = numericDateMatch[1]; if (rawDate.length === 6 && (version === rawDate || !specificDateMatch)) { - const year = Number.parseInt(rawDate.slice(0, 2), 10); - const month = Number.parseInt(rawDate.slice(2, 4), 10); - const day = Number.parseInt(rawDate.slice(4, 6), 10); + const year = Number(rawDate.slice(0, 2)); + const month = Number(rawDate.slice(2, 4)); + const day = Number(rawDate.slice(4, 6)); const fullYear = year < 70 ? 2000 + year : 1900 + year; pubDateStr = `${fullYear}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`; } diff --git a/lib/routes/dayanzai/index.ts b/lib/routes/dayanzai/index.ts index abb37cb3ab5c..dfd56492f744 100644 --- a/lib/routes/dayanzai/index.ts +++ b/lib/routes/dayanzai/index.ts @@ -41,7 +41,7 @@ async function handler(ctx) { const response = await got.get(currentUrl); const $ = load(response.data); const lists = $('div.c-box > div > div.c-zx-list > ul > li'); - const reg = /日期:(.*?(\s\(.*?\))?)\s/; + const reg = /日期:(.*?(?:\s\(.*?\))?)\s/; const list = lists.toArray().map((item) => { item = $(item).find('div'); let date = reg.exec(item.find('div.r > p.other').text())[1]; @@ -56,7 +56,7 @@ async function handler(ctx) { } return { title: item.find('div.r > p.r-top > span > a').text(), - pubDate: timezone(date, +8), + pubDate: timezone(date, 8), description: item.find('div.r > p.desc').text(), link: item.find('div.r > p.r-top > span > a').attr('href'), }; diff --git a/lib/routes/dbaplus/new.ts b/lib/routes/dbaplus/new.ts index 7e877f78a079..5d6d5379cac6 100644 --- a/lib/routes/dbaplus/new.ts +++ b/lib/routes/dbaplus/new.ts @@ -14,7 +14,7 @@ import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { id = '9' } = 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://dbaplus.cn'; const targetUrl: string = new URL(`news-${id}-1.html`, baseUrl).href; @@ -113,14 +113,14 @@ export const handler = async (ctx: Context): Promise => { const processedItem: DataItem = { title, description, - pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : item.pubDate, + pubDate: pubDateStr ? timezone(parseDate(pubDateStr), 8) : item.pubDate, category: categories, author: authors, content: { html: description, text: description, }, - updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : item.updated, + updated: upDatedStr ? timezone(parseDate(upDatedStr), 8) : item.updated, language, }; @@ -135,13 +135,13 @@ export const handler = async (ctx: Context): Promise => { const description: string = $('meta[name="description"]').attr('content') ?? ''; return { - title: $('title').text().split(/:/)[0], + title: $('title').text().split(/:/, 1)[0], description, link: targetUrl, item: items, allowEmpty: true, image: $('div.navbar-header img').attr('src'), - author: description.split(/:/)[0], + author: description.split(/:/, 1)[0], language, id: targetUrl, }; diff --git a/lib/routes/dblp/publication.ts b/lib/routes/dblp/publication.ts index fd2112ac059a..5326503d7205 100644 --- a/lib/routes/dblp/publication.ts +++ b/lib/routes/dblp/publication.ts @@ -32,9 +32,7 @@ async function handler(ctx) { // 发送 HTTP GET 请求到 API 并解构返回的数据对象 const { - result: { - hits: { hit: data }, - }, + result: { hits }, } = await ofetch('https://dblp.org/search/publ/api', { query: { q: field, @@ -45,6 +43,7 @@ async function handler(ctx) { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', }, }); + const { hit: data } = hits; // console.log(data); diff --git a/lib/routes/dcard/section.ts b/lib/routes/dcard/section.ts index a32f14582405..9c30bc07c7fa 100644 --- a/lib/routes/dcard/section.ts +++ b/lib/routes/dcard/section.ts @@ -26,7 +26,7 @@ export const route: Route = { async function handler(ctx) { const { type = 'latest', section = 'posts' } = ctx.req.param(); const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30; - const browser = await playwright(); + const context = await playwright(); let link = 'https://www.dcard.tw/f'; let api = 'https://www.dcard.tw/service/api/v2'; @@ -48,10 +48,10 @@ async function handler(ctx) { title += '最新'; } - 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.setExtraHTTPHeaders({ referer: `https://www.dcard.tw/f/${section}`, @@ -60,7 +60,7 @@ async function handler(ctx) { await page.goto(`${api}&limit=100`); await page.waitForSelector('body > pre'); const response = await page.evaluate(() => document.querySelector('body > pre').textContent); - const cookies = await cache.tryGet('dcard:cookies', () => page.cookies(), 3600, false); + const cookies = await cache.tryGet('dcard:cookies', () => page.context().cookies(), 3600, false); await page.close(); const data = JSON.parse(response); @@ -76,8 +76,8 @@ async function handler(ctx) { })); // parse fulltext for first `limit` items - const result = await utils.ProcessFeed(items, cookies, browser, limit, cache); - await browser.close(); + const result = await utils.ProcessFeed(items, cookies, context, limit, cache); + await context.close(); return { title, diff --git a/lib/routes/dcard/utils.ts b/lib/routes/dcard/utils.ts index 083c736d6dd6..1862c50d0c7d 100644 --- a/lib/routes/dcard/utils.ts +++ b/lib/routes/dcard/utils.ts @@ -1,34 +1,33 @@ import pMap from 'p-map'; -const ProcessFeed = async (items, cookies, browser, limit, cache) => { +const ProcessFeed = async (items, cookies, context, limit, cache) => { let newCookies = []; const result = await pMap( items.slice(0, limit), async (i) => { const url = `https://www.dcard.tw/service/api/v2/posts/${i.id}`; const content = await cache.tryGet(`dcard:${i.id}`, async () => { - let response; // try catch 处理被删除的帖子 try { - const page = await browser.newPage(); - await page.setRequestInterception(true); - page.on('request', (request) => { - request.resourceType() === 'document' || request.resourceType() === 'script' || request.resourceType() === 'fetch' || request.resourceType() === 'xhr' ? request.continue() : request.abort(); + const page = await context.newPage(); + await page.route('**/*', (route) => { + const request = route.request(); + request.resourceType() === 'document' || request.resourceType() === 'script' || request.resourceType() === 'fetch' || request.resourceType() === 'xhr' ? route.continue() : route.abort(); }); await page.setExtraHTTPHeaders({ referer: `https://www.dcard.tw/f/${i.forumAlias}/p/${i.id}`, }); - await page.setCookie(...cookies); + await page.context().addCookies(cookies); await page.goto(url); await page.waitForSelector('body > pre'); - response = await page.evaluate(() => document.querySelector('body > pre').textContent); - newCookies = await page.cookies(); + const response = await page.evaluate(() => document.querySelector('body > pre').textContent); + newCookies = await page.context().cookies(); await page.close(); const data = JSON.parse(response); let body = data.content; body = body.replaceAll(/(?=https?:\/\/).*?(?<=\.(jpe?g|gif|png))/gi, (m) => ``); - body = body.replaceAll(/(?=https?:\/\/).*(??)$/gim, (m) => `${m}`); + body = body.replaceAll(/(?=https?:\/\/).+(??)$/gim, (m) => `${m}`); body = body.replaceAll('\n', '
'); return body; diff --git a/lib/routes/dcfever/utils.tsx b/lib/routes/dcfever/utils.tsx index 487067ee34a7..7045d8fa49d5 100644 --- a/lib/routes/dcfever/utils.tsx +++ b/lib/routes/dcfever/utils.tsx @@ -76,7 +76,7 @@ const parseItem = (item) => content.find('img').each((_, e) => { if (e.attribs.src?.includes('?')) { - e.attribs.src = e.attribs.src.split('?')[0]; + e.attribs.src = e.attribs.src.split('?', 1)[0]; } }); diff --git a/lib/routes/ddosi/index.ts b/lib/routes/ddosi/index.ts index 7035be4d5082..e1e20c0a12b4 100644 --- a/lib/routes/ddosi/index.ts +++ b/lib/routes/ddosi/index.ts @@ -24,7 +24,7 @@ async function handler() { const url = 'https://www.ddosi.org/'; const response = await got({ method: 'get', - url: String(url), + url, headers: { Referer: url, }, @@ -51,7 +51,7 @@ async function handler() { return { title: '雨苁', - link: String(url), + link: url, item: items, }; } diff --git a/lib/routes/deadbydaylight/index.ts b/lib/routes/deadbydaylight/index.ts index acc689c0ba1d..06a32e6ad1c8 100644 --- a/lib/routes/deadbydaylight/index.ts +++ b/lib/routes/deadbydaylight/index.ts @@ -43,8 +43,8 @@ async function handler() { // { 0: node: { id, locale, slug, title, excerpt, image, published_at, article_category}} const items = await Promise.all( - Object.keys(articleMeta).map((id) => { - const content = articleMeta[id].node; + Object.values(articleMeta).map((edge) => { + const content = edge.node; const slug = content.slug; const dataUrl = `${baseUrl}/page-data/news/${slug}/page-data.json`; diff --git a/lib/routes/deadline/posts.tsx b/lib/routes/deadline/posts.tsx index dd9a5276988a..0d285abb2fc2 100644 --- a/lib/routes/deadline/posts.tsx +++ b/lib/routes/deadline/posts.tsx @@ -54,7 +54,7 @@ async function handler(ctx) { $('.c-lazy-image__img').each((_, img) => { img = $(img); if (img.attr('data-lazy-src')) { - img.attr('src', img.attr('data-lazy-src').split('?')[0]); + img.attr('src', img.attr('data-lazy-src').split('?', 1)[0]); img.removeAttr('data-lazy-src'); img.removeAttr('data-lazy-srcset'); } diff --git a/lib/routes/dealstreetasia/home.ts b/lib/routes/dealstreetasia/home.ts index e0959e5b0ef0..177bbf62e7ce 100644 --- a/lib/routes/dealstreetasia/home.ts +++ b/lib/routes/dealstreetasia/home.ts @@ -60,7 +60,7 @@ async function fetchPage() { link: item.post_url || item.link || '', description: item.post_excerpt || item.excerpt || '', pubDate: item.post_date ? new Date(item.post_date).toUTCString() : item.date ? new Date(item.date).toUTCString() : '', - category: item.category_link ? item.category_link.replaceAll(/(<([^>]+)>)/gi, '') : '', // Clean HTML if category_link exists + category: item.category_link ? item.category_link.replaceAll(/(<([^>]+)>)/g, '') : '', // Clean HTML if category_link exists image: item.image_url ? item.image_url.replace(/\?.*$/, '') : '', // Remove query parameters if image_url exists })); diff --git a/lib/routes/decrypt/index.ts b/lib/routes/decrypt/index.ts index 6d9896371dcc..78fef7c0ea2b 100644 --- a/lib/routes/decrypt/index.ts +++ b/lib/routes/decrypt/index.ts @@ -52,7 +52,7 @@ async function handler(ctx): Promise { const result = await extractFullText(item.link); return { title: item.title || 'Untitled', - link: item.link.split('?')[0], // Clean URL by removing query parameters + link: item.link.split('?', 1)[0], // Clean URL by removing query parameters pubDate: item.pubDate ? parseDate(item.pubDate) : undefined, description: result?.fullText ?? (item.content || ''), author: item.creator || 'Decrypt', @@ -66,7 +66,7 @@ async function handler(ctx): Promise { // Fallback to RSS content return { title: item.title || 'Untitled', - link: item.link.split('?')[0], + link: item.link.split('?', 1)[0], pubDate: item.pubDate ? parseDate(item.pubDate) : undefined, description: item.content || '', author: item.creator || 'Decrypt', diff --git a/lib/routes/dedao/articles.ts b/lib/routes/dedao/articles.ts index aa618daa4d5f..fab29eb49590 100644 --- a/lib/routes/dedao/articles.ts +++ b/lib/routes/dedao/articles.ts @@ -7,7 +7,7 @@ import got from '@/utils/got'; export const route: Route = { path: '/articles/:id?', categories: ['new-media'], - example: '/articles/9', // 示例路径更新 + example: '/dedao/articles/9', parameters: { id: '文章类型 ID,8 为得到头条,9 为得到精选,默认为 8' }, features: { requireConfig: false, diff --git a/lib/routes/dedao/index.ts b/lib/routes/dedao/index.ts index 773cf893b1d1..641932b74cea 100644 --- a/lib/routes/dedao/index.ts +++ b/lib/routes/dedao/index.ts @@ -33,7 +33,7 @@ async function handler(ctx) { let items = (category === 'news' ? data.news : category === 'figure' ? data.figure : data.videoList).map((item) => ({ title: item.title, pubDate: parseDate(item.online_time), - link: `${rootUrl}/${category === 'news' ? 'article/' : category === 'figure' ? 'people/' : ''}${item.online_time.split('T')[0].split('-').join('')}/${item.token}`, + link: `${rootUrl}/${category === 'news' ? 'article/' : category === 'figure' ? 'people/' : ''}${item.online_time.split('T', 1)[0].split('-').join('')}/${item.token}`, })); items = await Promise.all( diff --git a/lib/routes/dedao/knowledge.tsx b/lib/routes/dedao/knowledge.tsx index 076b8e9a7ba4..6651fc48bf7f 100644 --- a/lib/routes/dedao/knowledge.tsx +++ b/lib/routes/dedao/knowledge.tsx @@ -5,7 +5,7 @@ import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -const mentionPattern = /<\u2267\u2746>{"name":"(.*?)","uid":"\d+","at":"1"}<\/\u2266\u2746>/g; +const mentionPattern = /<\u{2267}\u{2746}>\{"name":"(.*?)","uid":"\d+","at":"1"\}<\/\u{2266}\u{2746}>/gu; const formatNoteText = (text = '') => text.replaceAll('\n\n', '

').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>

relevant config'); } - return config.discourse.config[ctx.req.param('configId')]; + return discourseConfig; } export { getConfig }; diff --git a/lib/routes/discuz/discuz.ts b/lib/routes/discuz/discuz.ts index e39d4f063eed..cd1e59c37eef 100644 --- a/lib/routes/discuz/discuz.ts +++ b/lib/routes/discuz/discuz.ts @@ -34,7 +34,7 @@ async function fetchWithAntiBot(url: string, header: Record) { if (initialHtml.includes('document.location.reload()')) { const setCookies = response.headers.getSetCookie?.() ?? []; - const cookieStr = setCookies.map((c) => c.split(';')[0]).join('; '); + const cookieStr = setCookies.map((c) => c.split(';', 1)[0]).join('; '); if (cookieStr) { response = await ofetch.raw(url, { method: 'get', @@ -104,7 +104,7 @@ async function handler(ctx) { // 若没有指定编码,则默认utf-8 const contentType = response.headers['content-type'] || ''; let $ = load(iconv.decode(responseData, 'utf-8')); - const charset = contentType.match(/charset=([^;]*)/)?.[1] ?? $('meta[charset]').attr('charset') ?? $('meta[http-equiv="Content-Type"]').attr('content')?.split('charset=')?.[1]; + const charset = contentType.match(/charset=([^;]*)/)?.[1] ?? $('meta[charset]').attr('charset') ?? $('meta[http-equiv="Content-Type"]').attr('content')?.split('charset=', 2)?.[1]; if (charset?.toLowerCase() !== 'utf-8') { $ = load(iconv.decode(responseData, charset ?? 'utf-8')); } @@ -120,7 +120,7 @@ async function handler(ctx) { // discuz 7.x 系列 // 支持全文抓取,限制抓取页面5个 const list = $('tbody[id^="normalthread"] > tr') - .slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 5) + .slice(0, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 5) .toArray() .map((item) => { item = $(item); @@ -147,7 +147,7 @@ async function handler(ctx) { // discuz X 系列 // 支持全文抓取,限制抓取页面5个 const list = $('tbody[id^="normalthread"] > tr') - .slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 5) + .slice(0, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 5) .toArray() .map((item) => { item = $(item); diff --git a/lib/routes/dlnews/category.tsx b/lib/routes/dlnews/category.tsx index a30277817a10..8f331c1ef7c2 100644 --- a/lib/routes/dlnews/category.tsx +++ b/lib/routes/dlnews/category.tsx @@ -69,12 +69,13 @@ const extractArticle = (item) => const { data: response } = await got(item.link); const $ = load(response); const scriptTagContent = $('script#fusion-metadata').text(); - const jsonData = JSON.parse(scriptTagContent.match(/Fusion\.globalContent=({.*?});Fusion\.globalContentConfig/)[1]).content_elements; + const jsonData = JSON.parse(scriptTagContent.match(/Fusion\.globalContent=(\{.*?\});Fusion\.globalContentConfig/)[1]).content_elements; const filteredData = []; for (const v of jsonData) { if (v.type === 'header' && v.content.includes('What we’re reading')) { break; - } else if (v.type === 'custom_embed' && Boolean(v.embed.config.text)) { + } + if (v.type === 'custom_embed' && Boolean(v.embed.config.text)) { filteredData.push({ type: v.type, data: v.embed.config.text }); } else if (v.type === 'text' && !v.content.includes('NOW READ: ')) { filteredData.push({ type: v.type, data: v.content }); diff --git a/lib/routes/dlsite/ci-en/article.ts b/lib/routes/dlsite/ci-en/article.ts index b05eb3dc6cbd..ee8642c8203b 100644 --- a/lib/routes/dlsite/ci-en/article.ts +++ b/lib/routes/dlsite/ci-en/article.ts @@ -75,7 +75,7 @@ async function handler(ctx) { }); item.description = content('article').html(); - item.pubDate = timezone(parseDate(content('.e-date').first().text()), +9); + item.pubDate = timezone(parseDate(content('.e-date').first().text()), 9); item.category = content('.c-hashTagList-item') .toArray() .map((t) => content(t).text().split('#').pop().trim()); diff --git a/lib/routes/dlsite/utils.ts b/lib/routes/dlsite/utils.ts index b13e050f0c61..115743fe8aa2 100644 --- a/lib/routes/dlsite/utils.ts +++ b/lib/routes/dlsite/utils.ts @@ -127,9 +127,9 @@ const ProcessItems = async (ctx) => { const detail = details[guid]; - const pubDate = timezone(parseDate(detail.regist_date), +9); + const pubDate = timezone(parseDate(detail.regist_date), 9); const discountRate = detail.discount_rate; - const discountEndDate = detail.discount_end_date ? timezone(parseDate(detail.discount_end_date, 'MM/DD HH:mm'), +9) : undefined; + const discountEndDate = detail.discount_end_date ? timezone(parseDate(detail.discount_end_date, 'MM/DD HH:mm'), 9) : undefined; images = images.length === 0 ? [detail.work_image] : images; return { diff --git a/lib/routes/dmzj/news.ts b/lib/routes/dmzj/news.ts index a99b1594a00f..03f72e609118 100644 --- a/lib/routes/dmzj/news.ts +++ b/lib/routes/dmzj/news.ts @@ -45,8 +45,8 @@ async function handler(ctx) { .map((item) => ({ title: $(item).find('h3 a').text(), link: $(item).find('h3 a').attr('href'), - author: $(item).find('.head_con_p_o span:nth-child(3)').text().split(':')[1], - pubDate: timezone(parseDate($(item).find('.head_con_p_o span').first().text(), 'YYYY-MM-DD HH:mm'), +8), + author: $(item).find('.head_con_p_o span:nth-child(3)').text().split(':', 2)[1], + pubDate: timezone(parseDate($(item).find('.head_con_p_o span').first().text(), 'YYYY-MM-DD HH:mm'), 8), description: $(item).find('p.com_about').text(), category: $(item) .find('.u_comfoot a .bqwarp') diff --git a/lib/routes/dn/news.ts b/lib/routes/dn/news.ts index 5d70ba919a40..d20e9a91f721 100644 --- a/lib/routes/dn/news.ts +++ b/lib/routes/dn/news.ts @@ -42,7 +42,7 @@ export const route: Route = { async function handler(ctx) { const { language, category = '' } = 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://dn.com'; const currentUrl = new URL(`/${language}/news/${category}`, rootUrl).href; @@ -75,7 +75,7 @@ async function handler(ctx) { .find('span.cat') .toArray() .map((c) => $(c).text()), - pubDate: timezone(parseDate(item.find('span.time').text()), +8), + pubDate: timezone(parseDate(item.find('span.time').text()), 8), }; }); @@ -100,7 +100,7 @@ async function handler(ctx) { .toArray() .map((c) => content(c).text()), ]; - item.pubDate = timezone(parseDate(content('span.date').text()), +8); + item.pubDate = timezone(parseDate(content('span.date').text()), 8); return item; }) diff --git a/lib/routes/dnaindia/common.ts b/lib/routes/dnaindia/common.ts index e501d9b50378..045efc462cfe 100644 --- a/lib/routes/dnaindia/common.ts +++ b/lib/routes/dnaindia/common.ts @@ -44,10 +44,10 @@ export async function handler(ctx) { .map((item) => $(item).find('a').text()); // Process date const timeText = $('p.dna-update').text(); - const dateMatch = timeText.match(/Updated\s*:\s*([\w\s,:\d]+?)(?:\s*\||$)/); + const dateMatch = timeText.match(/Updated\s*:([\w\s,:]+)/); let time = dateMatch ? dateMatch[1].trim() : ''; time = time.replace(/\s+IST$/, ''); - const pubDate = timezone(parseDate(time), +5.5); + const pubDate = timezone(parseDate(time), 5.5); // Get author information const authorMeta = $('meta[name="author"]').attr('content'); const author = authorMeta || 'DNA Web Team'; diff --git a/lib/routes/dnaindia/news.ts b/lib/routes/dnaindia/news.ts index 51191db282ca..80c055e6c91b 100644 --- a/lib/routes/dnaindia/news.ts +++ b/lib/routes/dnaindia/news.ts @@ -5,7 +5,7 @@ import { handler } from './common'; export const route: Route = { name: 'News', maintainers: ['Rjnishant530'], - path: ['/:category'], + path: '/:category', example: '/dnaindia/headlines', parameters: { category: 'Find it in the URL, or tables below', diff --git a/lib/routes/dnaindia/topic.ts b/lib/routes/dnaindia/topic.ts index 0a20bee67000..bf3f6d804d0a 100644 --- a/lib/routes/dnaindia/topic.ts +++ b/lib/routes/dnaindia/topic.ts @@ -5,7 +5,7 @@ import { handler } from './common'; export const route: Route = { name: 'Topic', maintainers: ['Rjnishant530'], - path: ['/topic/:topic'], + path: '/topic/:topic', example: '/dnaindia/topic/dna-verified', parameters: { category: 'Find it in the URL', diff --git a/lib/routes/dol/announce.ts b/lib/routes/dol/announce.ts index a76e5f44d79a..40f0f9f09733 100644 --- a/lib/routes/dol/announce.ts +++ b/lib/routes/dol/announce.ts @@ -95,7 +95,7 @@ async function handler(ctx) { Number.parseInt(dateList[1]) - 1, Number.parseInt(dateList[0]) ), - +7 + 7 ), author: officeName.text(), category: [reqType.text()], diff --git a/lib/routes/domp4/detail.ts b/lib/routes/domp4/detail.ts index ddeaf4515008..a8500872b358 100644 --- a/lib/routes/domp4/detail.ts +++ b/lib/routes/domp4/detail.ts @@ -30,7 +30,7 @@ function getDomList($, detailUrl) { export function getItemList($, detailUrl, second) { const encoded = $('.article script[type]') .text() - .match(/return p}\('(.*)',(\d+),(\d+),'(.*)'.split\(/); + .match(/return p\}\('(.*)',(\d+),(\d+),'(.*)'.split\(/); // 若 script 标签没有内容,直接解析 dom if (!encoded) { return getDomList($, detailUrl); diff --git a/lib/routes/domp4/utils.ts b/lib/routes/domp4/utils.ts index c01cc48142c6..04ec09a53e61 100644 --- a/lib/routes/domp4/utils.ts +++ b/lib/routes/domp4/utils.ts @@ -1,4 +1,3 @@ -/* eslint-disable unicorn/prefer-code-point */ import { config } from '@/config'; import ConfigNotFoundError from '@/errors/types/config-not-found'; @@ -62,9 +61,10 @@ function getUrlType(url) { */ function decodeCipherText(p, a, c, k, e, d) { e = function (c) { - return (c < a ? '' : e(Number.parseInt((c / a).toString()))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36)); + // oxlint-disable-next-line unicorn-js/operator-assignment + return (c < a ? '' : e(Number.parseInt((c / a).toString()))) + ((c = c % a) > 35 ? String.fromCodePoint(c + 29) : c.toString(36)); }; - if (!''.replace(/^/, String)) { + if (!''.replace(/^/, () => '')) { while (c--) { d[e(c.toString())] = k[c] || e(c.toString()); } @@ -79,8 +79,10 @@ function decodeCipherText(p, a, c, k, e, d) { c = 1; } while (c--) { - if (k[c]) { - p = p.replaceAll(new RegExp(String.raw`\b` + e(c.toString()) + String.raw`\b`, 'g'), k[c]); + const replacement = k[c]; + if (replacement) { + const token = e(c.toString()); + p = p.replaceAll(new RegExp(String.raw`\b` + token + String.raw`\b`, 'g'), () => replacement); } } return p; diff --git a/lib/routes/dongqiudi/utils.ts b/lib/routes/dongqiudi/utils.ts index c927dd76828b..2b27887d450d 100644 --- a/lib/routes/dongqiudi/utils.ts +++ b/lib/routes/dongqiudi/utils.ts @@ -59,7 +59,7 @@ const ProcessImg = (content) => { delete img.attribs['orig-src']; delete img.attribs['data-src']; } - img.attribs.src = img.attribs.src.includes('?watermark') ? img.attribs.src.split('?watermark')[0] : img.attribs.src; + img.attribs.src = img.attribs.src.includes('?watermark') ? img.attribs.src.split('?watermark', 1)[0] : img.attribs.src; }); }; @@ -145,7 +145,7 @@ const ProcessFeedType3 = (item, response) => { const initialState = JSON.parse( $('script:contains("window.__INITIAL_STATE__")') .text() - .match(/window\.__INITIAL_STATE__\s*=\s*(.*?);\(/)[1] + .match(/window\.__INITIAL_STATE__\s*=\s*((?:\S.*?)??);\(/)[1] ); // filter out undefined item diff --git a/lib/routes/dora-world/article.ts b/lib/routes/dora-world/article.ts index c903ee1cce78..96db87406b12 100644 --- a/lib/routes/dora-world/article.ts +++ b/lib/routes/dora-world/article.ts @@ -51,7 +51,7 @@ async function handler(ctx): Promise { title: item.title, link: item.page_url.startsWith('http') ? item.page_url : `${baseUrl}${item.page_url}`, description: item.page_url.startsWith('/contents/') ? '' : `

${item.title}

`, - pubDate: timezone(parseDate(item.publish_at), +9), + pubDate: timezone(parseDate(item.publish_at), 9), category: item.tags.map((tag) => tag.name), guid: item.id, })); @@ -70,7 +70,7 @@ async function handler(ctx): Promise { return item; }) ) - ).then((items) => items.filter((item) => item !== null))) as DataItem[], + )) as DataItem[], }; } @@ -85,6 +85,6 @@ async function getContent(nextBuildId: string, contentId: string) { content .html() ?.replaceAll(rubyRegex, '$1($2)') - ?.replaceAll(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFDCF\uFDE0-\uFFFD]/gm, '') ?? ''; + ?.replaceAll(/[^\t\n\r\u{0020}-\u{D7FF}\u{E000}-\u{FDCF}\u{FDE0}-\u{FFFD}]/gu, '') ?? ''; return description; } diff --git a/lib/routes/douban/channel/topic.ts b/lib/routes/douban/channel/topic.ts index c91dbf5cbe15..673e06a97a28 100644 --- a/lib/routes/douban/channel/topic.ts +++ b/lib/routes/douban/channel/topic.ts @@ -75,9 +75,8 @@ async function handler(ctx) { pubDate: new Date(item.create_time), link: item.url, }; - } else { - return null; } + return null; }) .filter(Boolean), allowEmpty: true, diff --git a/lib/routes/douban/other/classification.ts b/lib/routes/douban/other/classification.ts index 56b0c2f36dce..8549bc8308d2 100644 --- a/lib/routes/douban/other/classification.ts +++ b/lib/routes/douban/other/classification.ts @@ -26,7 +26,7 @@ export const route: Route = { async function handler(ctx) { const sort = ctx.req.param('sort') || 'U'; - const score = Number.parseFloat(ctx.req.param('score')) || 0; + const score = Number(ctx.req.param('score')) || 0; const tags = ctx.req.param('tags') || ''; const response = await got({ @@ -41,7 +41,7 @@ async function handler(ctx) { link: 'https://movie.douban.com/tag/#/?sort=U&range=0,10&tags=', item: movies .map((item) => { - const itemScore = Number.parseFloat(item.rate) || 0; + const itemScore = Number(item.rate) || 0; return itemScore >= score ? { diff --git a/lib/routes/douban/other/doulist.ts b/lib/routes/douban/other/doulist.ts index 77bf929e9fed..549083679dd5 100644 --- a/lib/routes/douban/other/doulist.ts +++ b/lib/routes/douban/other/doulist.ts @@ -52,7 +52,7 @@ async function handler(ctx) { description = $(item).find('span.status-recommend-text').text().trim(); } - if (type === '来自:豆瓣电影' || type === '来自:豆瓣' || type === '来自:豆瓣读书' || type === '来自:豆瓣音乐') { + if (['来自:豆瓣电影', '来自:豆瓣', '来自:豆瓣读书', '来自:豆瓣音乐'].includes(type)) { title = $(item).find('div.bd.doulist-subject div.title a').text().trim(); link = $(item).find('div.bd.doulist-subject div.title a').attr('href'); diff --git a/lib/routes/douban/other/playing.ts b/lib/routes/douban/other/playing.ts index 944c4b8a4f33..750ef4cd6373 100644 --- a/lib/routes/douban/other/playing.ts +++ b/lib/routes/douban/other/playing.ts @@ -22,7 +22,7 @@ export const route: Route = { }; async function handler(ctx) { - const score = Number.parseFloat(ctx.req.param('score')) || 0; + const score = Number(ctx.req.param('score')) || 0; const response = await got({ method: 'get', url: 'https://movie.douban.com/cinema/nowplaying/beijing', @@ -36,7 +36,7 @@ async function handler(ctx) { .toArray() .map((i) => { const item = $(i); - const itemScore = Number.parseFloat(item.attr('data-score')) || 0; + const itemScore = Number(item.attr('data-score')) || 0; return itemScore >= score ? { title: item.attr('data-title'), diff --git a/lib/routes/douban/other/replied.ts b/lib/routes/douban/other/replied.ts index 23767e6ce856..bf28f17fbd8f 100644 --- a/lib/routes/douban/other/replied.ts +++ b/lib/routes/douban/other/replied.ts @@ -56,7 +56,7 @@ async function handler(ctx) { method: 'get', url: item.link, }); - const match = detailResponse.data.match(/'comments':(.*)}],/); + const match = detailResponse.data.match(/'comments':(.*)\}\],/); if (match.length > 1) { const content = load(detailResponse.data); @@ -69,7 +69,8 @@ async function handler(ctx) { pubDate, author; - for (const c of comments) { + while (comments.length > 0) { + const c = comments.shift(); if (c.author.uid === ctx.req.param('uid') && new Date(c.create_time) > new Date(latest)) { latest = new Date(c.create_time + ' GMT+8'); pubDate = latest.toUTCString(); diff --git a/lib/routes/douban/other/replies.ts b/lib/routes/douban/other/replies.ts index 7d2515a6137a..a330635f2203 100644 --- a/lib/routes/douban/other/replies.ts +++ b/lib/routes/douban/other/replies.ts @@ -55,10 +55,11 @@ async function handler(ctx) { url: item.link, }); - const comments = JSON.parse(detailResponse.data.match(/'comments':(.*)}],/)[1] + '}]'); + const comments = JSON.parse(detailResponse.data.match(/'comments':(.*)\}\],/)[1] + '}]'); - for (const c of comments) { - if (c.id === item.link.split('#')[1]) { + while (comments.length > 0) { + const c = comments.shift(); + if (c.id === item.link.split('#', 2)[1]) { return { link: item.link, title: `${c.author.name} 于 ${c.create_time} 的回应`, @@ -66,7 +67,8 @@ async function handler(ctx) { description: c.text, author: c.author.name, }; - } else if (c.replies.length > 0) { + } + if (c.replies.length > 0) { comments.push(...c.replies); } } diff --git a/lib/routes/douban/other/topic.ts b/lib/routes/douban/other/topic.ts index e5c56ea4b36f..86c853bc9079 100644 --- a/lib/routes/douban/other/topic.ts +++ b/lib/routes/douban/other/topic.ts @@ -54,7 +54,7 @@ async function handler(ctx) { let link; let title; if (type === 'status') { - link = item.target.status.sharing_url.split('&')[0]; + link = item.target.status.sharing_url.split('&', 1)[0]; author = item.target.status.author.name; title = author + '的广播'; date = item.target.status.create_time; diff --git a/lib/routes/douban/people/status.ts b/lib/routes/douban/people/status.ts index f5a225d4fee4..cf7b8cdcd284 100644 --- a/lib/routes/douban/people/status.ts +++ b/lib/routes/douban/people/status.ts @@ -101,7 +101,7 @@ function tryFixStatus(status) { } if (status.sharing_url) { - status.sharing_url = status.sharing_url.split('&')[0]; + status.sharing_url = status.sharing_url.split('&', 1)[0]; } if (!result.isFixSuccess) { @@ -313,10 +313,7 @@ function getContentByActivity(ctx, item, params = {}, picsPrefixes = []) { } picsPrefixes.push(picsPrefix); - const imageUrls: Array = []; - for (const image of status.images) { - imageUrls.push(image?.large?.url); - } + const imageUrls: Array = Array.from(status.images, (image) => image?.large?.url); description += prepareImages(imageUrls); } @@ -412,10 +409,7 @@ function getContentByActivity(ctx, item, params = {}, picsPrefixes = []) { description += '
'; } if (status.card.images_block) { - const imageUrls: Array = []; - for (const image of status.card.images_block.images) { - imageUrls.push(image.image?.large?.url); - } + const imageUrls: Array = Array.from(status.card.images_block.images, (image) => image.image?.large?.url); description += prepareImages(imageUrls); } } diff --git a/lib/routes/douban/people/wish.ts b/lib/routes/douban/people/wish.ts index bd676988bb78..703767c573cb 100644 --- a/lib/routes/douban/people/wish.ts +++ b/lib/routes/douban/people/wish.ts @@ -1,5 +1,3 @@ -import querystring from 'node:querystring'; - import { load } from 'cheerio'; import { config } from '@/config'; @@ -8,10 +6,10 @@ import cache from '@/utils/cache'; import got from '@/utils/got'; export const route: Route = { - path: '/people/:userid/wish/:routeParams?', + path: '/people/:userid/wish', categories: ['social-media'], example: '/douban/people/exherb/wish', - parameters: { userid: '用户id', routeParams: '额外参数;见下' }, + parameters: { userid: '用户id' }, features: { requireConfig: false, requirePuppeteer: false, @@ -23,76 +21,51 @@ export const route: Route = { name: '用户想看', maintainers: ['exherb'], handler, - description: `对于豆瓣用户想看的内容,在 \`routeParams\` 参数中以 query string 格式设置如下选项可以控制输出的样式 - -| 键 | 含义 | 接受的值 | 默认值 | -| ---------- | ---------- | -------- | ------ | -| pagesCount | 查询页面数 | | 1 |`, }; async function handler(ctx) { const userid = ctx.req.param('userid'); - const routeParams = querystring.parse(ctx.req.param('routeParams')); - - let userName; - - const pageSize = 15; - const pagesCount = routeParams.pagesCount ? Number.parseInt(routeParams.pagesCount) : 1; - const tasks = []; - for (let page = 0; page < pagesCount; page += 1) { - const url = `https://movie.douban.com/people/${userid}/wish?start=${page * pageSize}`; - - tasks.push( - 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 - ) - .then((data) => { - const $ = load(data); - const list = $('div.article > div.grid-view > div.item'); - userName = userName || $('div.side-info-txt > h3').text(); - if (list) { - return Promise.all( - list.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(); - const rssItem = { - title: title_, - description: `${info.find('.intro').text()}
`, - 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..f1ef14cb9a0a 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); @@ -88,13 +88,13 @@ async function handler(ctx) { videoList = videoList.map((item) => proxyVideo(item, relay)); } let duration = post.video && post.video.duration; - duration = duration && duration / 1000; + duration &&= duration / 1000; let img; // if (!embed) { // img = post.video && post.video.dynamic_cover && post.video.dynamic_cover.url_list[post.video.dynamic_cover.url_list.length - 1]; // dynamic cover (webp) // } - img = img || (post.video && post.video.origin_cover && post.video.origin_cover.url_list.at(-1)); - img = img && resolveUrl(img); + img ||= post.video && post.video.origin_cover && post.video.origin_cover.url_list.at(-1); + img &&= resolveUrl(img); // render description const desc = post.desc && post.desc.replaceAll('\n', '
'); 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..f4421eb0047b 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.'); @@ -96,16 +95,15 @@ async function handler(ctx) { videoList = videoList.map((item) => proxyVideo(item, relay)); } let duration = post.video?.duration; - duration = duration && duration / 1000; + duration &&= duration / 1000; let img; // if (!embed) { // img = post.video && post.video.dynamicCover; // dynamic cover (webp) // } - img = - img || + img ||= post.video?.cover?.url_list.at(-1) || // HD post.video?.origin_cover?.url_list.at(-1); // LD - img = img && resolveUrl(img); + img &&= resolveUrl(img); // render description const desc = post.desc?.replaceAll('\n', '
'); @@ -114,7 +112,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/douyu/group.ts b/lib/routes/douyu/group.ts index 93893ebff7c4..631a4e53b787 100644 --- a/lib/routes/douyu/group.ts +++ b/lib/routes/douyu/group.ts @@ -54,7 +54,7 @@ async function handler(ctx) { const items = response.data.data.map((item) => ({ title: item.title, link: `${rootUrl}/p/${item.post_id}`, - pubDate: timezone(parseDate(item.created_at_std), +8), + pubDate: timezone(parseDate(item.created_at_std), 8), description: renderDescription({ content: item.describe, images: item.imglist.map((i) => ({ diff --git a/lib/routes/dpm/exhibitions.tsx b/lib/routes/dpm/exhibitions.tsx index e33df05dce79..dd9053744005 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(); @@ -126,7 +126,7 @@ export const route: Route = { fullDuration = '未定/常设'; } - const cleanDuration = fullDuration.replaceAll('.', '-').replaceAll('/', '-'); + const cleanDuration = fullDuration.replaceAll(/[./]/g, '-'); // use YYYY-MM-DD for date format const dateMatches = cleanDuration.match(/\d{4}-\d{2}-\d{2}/g); 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..39688307bd64 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 ?? []), @@ -135,7 +135,7 @@ export const handler = async (ctx: Context): Promise => { const processedItem: DataItem = { title, description, - pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : item.pubDate, + pubDate: pubDateStr ? timezone(parseDate(pubDateStr), 8) : item.pubDate, category: categories, author: authors, content: { @@ -144,7 +144,7 @@ export const handler = async (ctx: Context): Promise => { }, image, banner: image, - updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : item.updated, + updated: upDatedStr ? timezone(parseDate(upDatedStr), 8) : item.updated, language, }; 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/dxy/board.ts b/lib/routes/dxy/board.ts index 11552325bf81..7c5654c1eb6f 100644 --- a/lib/routes/dxy/board.ts +++ b/lib/routes/dxy/board.ts @@ -85,7 +85,7 @@ async function handler(ctx) { postId: item.postId, })); - const items = await Promise.all(list.map((item) => getPost(item, cache.tryGet))); + const items = await Promise.all(list.map((item) => getPost(item))); return { title: boardDetail.title, diff --git a/lib/routes/dxy/profile/thread.ts b/lib/routes/dxy/profile/thread.ts index 3aae46ea1bd6..ccdfce162650 100644 --- a/lib/routes/dxy/profile/thread.ts +++ b/lib/routes/dxy/profile/thread.ts @@ -96,7 +96,7 @@ async function handler(ctx) { }; }); - const items = await Promise.all(list.map((item) => getPost(item, cache.tryGet))); + const items = await Promise.all(list.map((item) => getPost(item))); return { title: `${userInfo.nickname} 的个人主页 - 丁香园论坛 - 专业医生社区,医学、药学、生命科学、科研学术交流`, diff --git a/lib/routes/dxy/special.ts b/lib/routes/dxy/special.ts index dbe27a8ba885..1a9b0a178da4 100644 --- a/lib/routes/dxy/special.ts +++ b/lib/routes/dxy/special.ts @@ -89,7 +89,7 @@ async function handler(ctx) { }; }); - const items = await Promise.all(list.map((item) => getPost(item, cache.tryGet))); + const items = await Promise.all(list.map((item) => getPost(item))); return { title: specialDetail.name, diff --git a/lib/routes/dxy/utils.ts b/lib/routes/dxy/utils.ts index 49f788ff9b81..844531d516a9 100644 --- a/lib/routes/dxy/utils.ts +++ b/lib/routes/dxy/utils.ts @@ -1,6 +1,7 @@ import * as cheerio from 'cheerio'; import CryptoJS from 'crypto-js'; +import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; @@ -33,8 +34,8 @@ const sign = (params) => { return CryptoJS.SHA1(searchParams.toString()).toString(); }; -const getPost = (item, tryGet) => - tryGet(item.link, async () => { +const getPost = (item) => + cache.tryGet(item.link, async () => { const postParams = { postId: item.postId, serverTimestamp: Date.now(), diff --git a/lib/routes/dykszx/news.ts b/lib/routes/dykszx/news.ts index d9f2273e3379..2199dfe865d6 100644 --- a/lib/routes/dykszx/news.ts +++ b/lib/routes/dykszx/news.ts @@ -47,7 +47,7 @@ async function handler(ctx) { title: item.children[0].children[0].data, description: newsContent.content, link: newsContent.newsPage, - pubDate: timezone(parseDate(newsContent.newsTime, '时间:YYYY-MM-DD HH:mm:ss'), +8), + pubDate: timezone(parseDate(newsContent.newsTime, '时间:YYYY-MM-DD HH:mm:ss'), 8), }; }); }) diff --git a/lib/routes/dytt/index.ts b/lib/routes/dytt/index.ts index d62cccaabbe1..0801008dae06 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; @@ -43,14 +43,14 @@ export const handler = async (ctx: Context): Promise => { const processedItem: DataItem = { title, description, - pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : undefined, + pubDate: pubDateStr ? timezone(parseDate(pubDateStr), 8) : undefined, link: linkUrl ? new URL(linkUrl, baseUrl).href : undefined, doi: $el.find('meta[name="citation_doi"]').attr('content'), content: { html: description, text: description, }, - updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : undefined, + updated: upDatedStr ? timezone(parseDate(upDatedStr), 8) : undefined, language, }; 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/eagle/changelog.ts b/lib/routes/eagle/changelog.ts index be577c12c8ee..a132e2e56940 100644 --- a/lib/routes/eagle/changelog.ts +++ b/lib/routes/eagle/changelog.ts @@ -38,8 +38,7 @@ async function handler(ctx) { } if (language === 'tw') { changelog = '更新日誌'; - } - if (language === 'en') { + } else if (language === 'en') { changelog = 'Release Notes'; } diff --git a/lib/routes/earthquake/ceic.ts b/lib/routes/earthquake/ceic.ts index ff980d1e9739..51f6acbbf83a 100644 --- a/lib/routes/earthquake/ceic.ts +++ b/lib/routes/earthquake/ceic.ts @@ -41,7 +41,7 @@ export const route: Route = { async function handler(ctx) { let type = Number(ctx.req.param('type')); - type = type ?? 1; + type ??= 1; const baseUrl = 'http://www.ceic.ac.cn'; const api = `${baseUrl}/ajax/speedsearch?num=${type}`; const mappings = { @@ -96,7 +96,7 @@ async function handler(ctx) { return { title: `${LOCATION_C}发生${M}级地震`, link: `${baseUrl}/${NEW_DID}.html`, - pubDate: timezone(parseDate(O_TIME, 'YYYY-MM-DD HH:mm:ss'), +8), + pubDate: timezone(parseDate(O_TIME, 'YYYY-MM-DD HH:mm:ss'), 8), description: contentBuilder.join('
'), guid: NEW_DID, }; diff --git a/lib/routes/earthquake/index.ts b/lib/routes/earthquake/index.ts index 3dfc0b7dfeb3..a13c841ddbb1 100644 --- a/lib/routes/earthquake/index.ts +++ b/lib/routes/earthquake/index.ts @@ -54,7 +54,7 @@ async function handler(ctx) { return { title: `${epicenter}发生${num}级地震`, link: `https://www.cea.gov.cn/eportal/ui?struts.portlet.mode=view&struts.portlet.action=/portlet/expressEarthquake!toNewInfoView.action&pageId=366521&id=${id}`, - pubDate: timezone(parseDate(date, 'YYYY-MM-DD HH:mm:ss'), +8), + pubDate: timezone(parseDate(date, 'YYYY-MM-DD HH:mm:ss'), 8), description, }; }); diff --git a/lib/routes/eastday/24.ts b/lib/routes/eastday/24.ts index 43ed7a699e04..3d89b9aa94d6 100644 --- a/lib/routes/eastday/24.ts +++ b/lib/routes/eastday/24.ts @@ -86,13 +86,13 @@ async function handler(ctx) { const pageNumber = Number.parseInt(detailResponse.data.match(/var page_num = '(\d+)'/)[1]); item.description = content('#J-contain_detail_cnt').html(); - item.pubDate = timezone(parseDate(content('meta[property="og:release_date"]').attr('content')), +8); + item.pubDate = timezone(parseDate(content('meta[property="og:release_date"]').attr('content')), 8); if (pageNumber > 1) { 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/eastday/portrait.ts b/lib/routes/eastday/portrait.ts index d68172890fc3..ae9dc2768e22 100644 --- a/lib/routes/eastday/portrait.ts +++ b/lib/routes/eastday/portrait.ts @@ -46,7 +46,7 @@ async function handler() { const list = response.data.list.map((item) => ({ link: item.url, title: item.title, - pubDate: timezone(parseDate(item.time), +8), + pubDate: timezone(parseDate(item.time), 8), })); const items = await Promise.all( diff --git a/lib/routes/eastday/sh.ts b/lib/routes/eastday/sh.ts index 7c875f2ddfb1..115be82e62ab 100644 --- a/lib/routes/eastday/sh.ts +++ b/lib/routes/eastday/sh.ts @@ -45,7 +45,7 @@ async function handler() { const entity = { title: item.title, description: item.abstracts, - pubDate: timezone(parseDate(item.time), +8), + pubDate: timezone(parseDate(item.time), 8), link, }; diff --git a/lib/routes/eastmoney/ttjj/user.ts b/lib/routes/eastmoney/ttjj/user.ts index 35d65099344e..37e7728b84bf 100644 --- a/lib/routes/eastmoney/ttjj/user.ts +++ b/lib/routes/eastmoney/ttjj/user.ts @@ -71,7 +71,7 @@ async function handler(ctx) { const single = { title: item.post_title, description, - pubDate: timezone(parseDate(item.post_display_time, 'YYYY-MM-DD HH:mm:ss'), +8), + pubDate: timezone(parseDate(item.post_display_time, 'YYYY-MM-DD HH:mm:ss'), 8), link: `https://fundbarmob.eastmoney.com/index.html?goPage=articleView&lastPage=personDetailView&aid=${item.post_id}`, }; return single; diff --git a/lib/routes/ecnu/art.ts b/lib/routes/ecnu/art.ts index ed057daaac8d..d4069f729a51 100644 --- a/lib/routes/ecnu/art.ts +++ b/lib/routes/ecnu/art.ts @@ -28,8 +28,8 @@ export const route: Route = { const links = $('ul.data-list > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('span').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('span').text()), 8), + 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..71f45cc62684 100644 --- a/lib/routes/ecnu/bksy.ts +++ b/lib/routes/ecnu/bksy.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.news_list.list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_date').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_date').text()), 8), + 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..72910d18ee40 100644 --- a/lib/routes/ecnu/cee.ts +++ b/lib/routes/ecnu/cee.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.news_list.list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_meta').text()), 8), + 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..fe255e7b5fae 100644 --- a/lib/routes/ecnu/chem.ts +++ b/lib/routes/ecnu/chem.ts @@ -28,8 +28,8 @@ export const route: Route = { const links = $('ul.cols_list.clearfix > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.cols_meta').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.cols_meta').text()), 8), + 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..c3d64dc9164a 100644 --- a/lib/routes/ecnu/chinese.ts +++ b/lib/routes/ecnu/chinese.ts @@ -28,8 +28,8 @@ export const route: Route = { const links = $('ul.news_list.list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_meta').text()), 8), + 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..2a077c302148 100644 --- a/lib/routes/ecnu/comm.ts +++ b/lib/routes/ecnu/comm.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.news_list.list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_meta').text()), 8), + 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..65604f2d9894 100644 --- a/lib/routes/ecnu/cs.ts +++ b/lib/routes/ecnu/cs.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('div#wp_news_w6 ul.data-list > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('span').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('span').text()), 8), + 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..b1fc8c20d569 100644 --- a/lib/routes/ecnu/cxcy.ts +++ b/lib/routes/ecnu/cxcy.ts @@ -49,8 +49,8 @@ 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(), + pubDate: timezone(parseDate($(el).find('.data').text()), 8), + 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..ffd5f6ecb29e 100644 --- a/lib/routes/ecnu/dase.ts +++ b/lib/routes/ecnu/dase.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.news_list.list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_meta').text()), 8), + 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..19c359650fb9 100644 --- a/lib/routes/ecnu/dx.ts +++ b/lib/routes/ecnu/dx.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.news_list.list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_meta').text()), 8), + 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..b924a5a77bbf 100644 --- a/lib/routes/ecnu/dxb.ts +++ b/lib/routes/ecnu/dxb.ts @@ -28,8 +28,8 @@ export const route: Route = { const links = $('ul.news_list > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_meta').text()), 8), + 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..510088a06e5f 100644 --- a/lib/routes/ecnu/ed.ts +++ b/lib/routes/ecnu/ed.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.news_list.list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_meta').text()), 8), + 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..16a447e445d9 100644 --- a/lib/routes/ecnu/geoai.ts +++ b/lib/routes/ecnu/geoai.ts @@ -28,8 +28,8 @@ export const route: Route = { const links = $('ul.news_list > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_meta').text()), 8), + 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..417bf1ab3939 100644 --- a/lib/routes/ecnu/ghcollege.ts +++ b/lib/routes/ecnu/ghcollege.ts @@ -28,8 +28,8 @@ export const route: Route = { const links = $('ul.wp_article_list > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.Article_PublishDate').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.Article_PublishDate').text()), 8), + 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..7af7d5a64a87 100644 --- a/lib/routes/ecnu/history.ts +++ b/lib/routes/ecnu/history.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.data-list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('span').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('span').text()), 8), + 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..b35ecccc2d21 100644 --- a/lib/routes/ecnu/jiaoliu.ts +++ b/lib/routes/ecnu/jiaoliu.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('#wp_news_w3 > table > tbody > tr') .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(), + pubDate: timezone(parseDate($(el).find('div[style="white-space:nowrap"]').text()), 8), + 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..05602979ae00 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,18 +43,21 @@ 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 item.description = '请进行统一身份认证之后再访问'; return item; } - } else { - // file to download - item.description = '点击认证后访问内容'; - return item; } + // file to download + item.description = '点击认证后访问内容'; + return item; }) ) ); diff --git a/lib/routes/ecnu/mks.ts b/lib/routes/ecnu/mks.ts index 891d801d97d7..3d56dec355c2 100644 --- a/lib/routes/ecnu/mks.ts +++ b/lib/routes/ecnu/mks.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.news_list.list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_date').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_date').text()), 8), + 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..5f7c0d9728eb 100644 --- a/lib/routes/ecnu/mxcsy.ts +++ b/lib/routes/ecnu/mxcsy.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.wp_article_list > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.Article_PublishDate').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.Article_PublishDate').text()), 8), + 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..bc8fa1cb353e 100644 --- a/lib/routes/ecnu/pharm.ts +++ b/lib/routes/ecnu/pharm.ts @@ -28,8 +28,8 @@ export const route: Route = { const links = $('ul.news_list.list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_meta').text()), 8), + 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..1fbc8d57c858 100644 --- a/lib/routes/ecnu/philo.ts +++ b/lib/routes/ecnu/philo.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.data-list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('span').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('span').text()), 8), + 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..528b483aa17e 100644 --- a/lib/routes/ecnu/phy.ts +++ b/lib/routes/ecnu/phy.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.data-list > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('span').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('span').text()), 8), + 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..07b056125c3e 100644 --- a/lib/routes/ecnu/psy.ts +++ b/lib/routes/ecnu/psy.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.wp_article_list > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.Article_PublishDate').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.Article_PublishDate').text()), 8), + 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..fce7194d2f01 100644 --- a/lib/routes/ecnu/sees.ts +++ b/lib/routes/ecnu/sees.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.news_list.list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_meta').text()), 8), + 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..5f5d367c15d6 100644 --- a/lib/routes/ecnu/sei.ts +++ b/lib/routes/ecnu/sei.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.data-list > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.data-list-time').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.data-list-time').text()), 8), + 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..6ec3bcb6e271 100644 --- a/lib/routes/ecnu/spm.ts +++ b/lib/routes/ecnu/spm.ts @@ -28,8 +28,8 @@ export const route: Route = { const links = $('ul.news_list.list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_meta').text()), 8), + 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..f23ef39a7e56 100644 --- a/lib/routes/ecnu/stat.ts +++ b/lib/routes/ecnu/stat.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.news_list.list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_meta').text()), 8), + 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..7b7a16d68787 100644 --- a/lib/routes/ecnu/tyxx.ts +++ b/lib/routes/ecnu/tyxx.ts @@ -26,8 +26,8 @@ export const route: Route = { const links = $('ul.news_list.list2 > li') .toArray() .map((el) => ({ - pubDate: timezone(parseDate($(el).find('.news_meta').text()), +8), - link: new URL($(el).find('a').attr('href'), baseUrl).toString(), + pubDate: timezone(parseDate($(el).find('.news_meta').text()), 8), + 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/ecust/e/news.ts b/lib/routes/ecust/e/news.ts index 2b982158f20d..ef4c0bd80aac 100644 --- a/lib/routes/ecust/e/news.ts +++ b/lib/routes/ecust/e/news.ts @@ -45,7 +45,7 @@ async function handler() { const list = response.data.datas.datas.map((item) => ({ title: item['1'].value, link: item.url.startsWith('http') ? item.url : `${baseUrl}/engine2/d/${item.id}/${item.engineInstanceId}/0`, - pubDate: timezone(parseDate(item['6'].value), +8), + pubDate: timezone(parseDate(item['6'].value), 8), })); const items = await Promise.all( diff --git a/lib/routes/eeo/kuaixun.ts b/lib/routes/eeo/kuaixun.ts index 28cf67b811ba..e1e235caa46b 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'; @@ -51,7 +51,7 @@ export const handler = async (ctx: Context): Promise => { const processedItem: DataItem = { title, description, - pubDate: pubDate ? timezone(parseDate(pubDate), +8) : undefined, + pubDate: pubDate ? timezone(parseDate(pubDate), 8) : undefined, link: linkUrl, category: categories, author: authors, @@ -63,7 +63,7 @@ export const handler = async (ctx: Context): Promise => { }, image, banner: image, - updated: updated ? timezone(parseDate(updated), +8) : undefined, + updated: updated ? timezone(parseDate(updated), 8) : undefined, language, }; @@ -93,13 +93,13 @@ export const handler = async (ctx: Context): Promise => { const processedItem: DataItem = { title, description, - pubDate: pubDateStr ? timezone(parseDate(pubDateStr), +8) : item.pubDate, + pubDate: pubDateStr ? timezone(parseDate(pubDateStr), 8) : item.pubDate, author: authors, content: { html: description, text: description, }, - updated: upDatedStr ? timezone(parseDate(upDatedStr), +8) : item.updated, + updated: upDatedStr ? timezone(parseDate(upDatedStr), 8) : item.updated, language, }; 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 = { + mundo: 'Mundo', + espana: 'España', + economia: 'Economía', + cultura: 'Cultura', + 'ciencia-y-tecnologia': 'Ciencia y Tecnología', + deportes: 'Deportes', + salud: 'Salud', + 'medio-ambiente': 'Medio Ambiente', + educacion: 'Educación', + 'euro-efe': 'EuroEFE', +}; + +export const route: Route = { + path: '/:category?', + name: 'Category', + maintainers: ['mlkgrnt'], + example: '/efe/mundo', + parameters: { + category: { + description: 'Category slug, see table below. Defaults to mundo.', + default: 'mundo', + }, + }, + handler, + categories: ['new-media'], + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['efe.com/:category'], + target: '/:category', + }, + ], +}; + +async function handler(ctx) { + const category = ctx.req.param('category') || 'mundo'; + const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20; + const pageUrl = `${rootUrl}/${category}/`; + + const response = await ofetch(pageUrl); + const $ = load(response); + + const links = $('.e-loop-item .elementor-widget-theme-post-title a[href]') + .toArray() + .map((el) => $(el).attr('href')) + .filter((href): href is string => !!href && href.startsWith(`${rootUrl}/${category}/`) && /\/\d{4}-\d{2}-\d{2}\//.test(href)); + + const items = await pMap( + links.slice(0, limit), + (link) => + cache.tryGet(link, async () => { + const detail = await ofetch(link); + const $detail = load(detail); + + const title = $detail('title').text(); + const dateMatch = detail.match(/"datePublished":\s*"([^"]+)"/); + const pubDate = dateMatch ? parseDate(dateMatch[1]) : undefined; + + const image = $detail('meta[property="og:image"]').attr('content'); + const content = $detail('.elementor-widget-theme-post-content'); + content.find('.auto-banner').remove(); + const description = (image ? `
` : '') + (content.html() || ''); + + return { + title, + link, + pubDate, + description, + }; + }), + { concurrency: 2 } + ); + + return { + title: `EFE Noticias - ${categories[category] || category}`, + link: pageUrl, + item: items, + }; +} diff --git a/lib/routes/efe/namespace.ts b/lib/routes/efe/namespace.ts new file mode 100644 index 000000000000..55dcbb41ebee --- /dev/null +++ b/lib/routes/efe/namespace.ts @@ -0,0 +1,7 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'EFE Noticias', + url: 'efe.com', + lang: 'es', +}; diff --git a/lib/routes/ehentai/ehapi.ts b/lib/routes/ehentai/ehapi.ts index ba9284beaa7c..2909546fbcbc 100644 --- a/lib/routes/ehentai/ehapi.ts +++ b/lib/routes/ehentai/ehapi.ts @@ -161,7 +161,7 @@ function getBittorrent(cache, bittorrent_page_url) { const match = onclick.match(/'(.*?)'/); if (match) { bittorrent_url = match[1]; - const match_p = bittorrent_url.match(/torrent\?p=(.*?)$/); + const match_p = bittorrent_url.match(/torrent\?p=(.*)$/); if (match_p) { p = match_p[1]; } @@ -178,10 +178,12 @@ function getBittorrent(cache, bittorrent_page_url) { function updateBittorrent_url(cache, items) { // 下种子文件需要动态密码,密码每几次请求就更新一次 for (const item of items) { - if (item.enclosure_url) { - item.enclosure_url = item.enclosure_url.replace(/torrent\?p=.*$/, `torrent?p=${p}`); - cache.set(item.bittorrent_page_url, item.enclosure_url); + if (!item.enclosure_url) { + continue; } + + item.enclosure_url = item.enclosure_url.replace(/torrent\?p=.*$/, () => `torrent?p=${p}`); + cache.set(item.bittorrent_page_url, item.enclosure_url); } return items; } @@ -193,13 +195,12 @@ async function gatherItemsByPage(cache, url, get_bittorrent = false, embed_thumb } async function getFavoritesItems(cache, favcat, inline_set, page, get_bittorrent = false, embed_thumb = false) { - const response = await ehgot(`favorites.php?favcat=${favcat}&inline_set=${inline_set}`); if (page) { return gatherItemsByPage(cache, `favorites.php?favcat=${favcat}&next=${page}`, get_bittorrent, embed_thumb); - } else { - const items = await parsePage(cache, response.data, get_bittorrent, embed_thumb); - return updateBittorrent_url(cache, items); } + const response = await ehgot(`favorites.php?favcat=${favcat}&inline_set=${inline_set}`); + const items = await parsePage(cache, response.data, get_bittorrent, embed_thumb); + return updateBittorrent_url(cache, items); } function getSearchItems(cache, params, page, get_bittorrent = false, embed_thumb = false) { diff --git a/lib/routes/elamigos/index.ts b/lib/routes/elamigos/index.ts index faa942b0a13d..6333a1d847eb 100644 --- a/lib/routes/elamigos/index.ts +++ b/lib/routes/elamigos/index.ts @@ -117,12 +117,13 @@ function sanitizeHtml(pageHtml: string): string { $page('script, style, link, nav').remove(); $page('*').each((_: number, elem: any) => { - if (elem.attribs) { - const attributes = Object.keys(elem.attribs); - for (const attr of attributes) { - if (attr.toLowerCase().startsWith('on')) { - $page(elem).removeAttr(attr); - } + if (!elem.attribs) { + return; + } + const attributes = Object.keys(elem.attribs); + for (const attr of attributes) { + if (attr.toLowerCase().startsWith('on')) { + $page(elem).removeAttr(attr); } } }); diff --git a/lib/routes/elasticsearch-cn/index.ts b/lib/routes/elasticsearch-cn/index.ts index 574c841d9ae7..4860663ece1d 100644 --- a/lib/routes/elasticsearch-cn/index.ts +++ b/lib/routes/elasticsearch-cn/index.ts @@ -58,7 +58,7 @@ async function handler(ctx) { title: a.text(), link: a.attr('href'), author: item.find('.aw-user-name').text(), - pubDate: timezone(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}/.test(pubDate) ? parseDate(pubDate) : parseRelativeDate(pubDate), +8), + pubDate: timezone(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}/.test(pubDate) ? parseDate(pubDate) : parseRelativeDate(pubDate), 8), }; }); diff --git a/lib/routes/eshukan/academic.ts b/lib/routes/eshukan/academic.ts index b9771dbd5182..2fab9c680aed 100644 --- a/lib/routes/eshukan/academic.ts +++ b/lib/routes/eshukan/academic.ts @@ -10,7 +10,7 @@ import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const { id = '1' } = 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.eshukan.com'; const currentUrl = new URL(`academic/index.aspx?cid=${id}`, rootUrl).href; @@ -42,7 +42,7 @@ export const handler = async (ctx) => { return { title, description, - pubDate: pubDate ? timezone(parseDate(pubDate), +8) : undefined, + pubDate: pubDate ? timezone(parseDate(pubDate), 8) : undefined, link: new URL(a.prop('href'), currentUrl).href, content: { html: description, @@ -69,7 +69,7 @@ export const handler = async (ctx) => { item.title = title; item.description = description; - item.pubDate = pubDate ? timezone(parseDate(pubDate), +8) : item.pubDate; + item.pubDate = pubDate ? timezone(parseDate(pubDate), 8) : item.pubDate; item.author = $$('div.author a').text(); item.content = { html: description, diff --git a/lib/routes/esquirehk/tag.tsx b/lib/routes/esquirehk/tag.tsx index 21dcda81de0b..e15384ec2d66 100644 --- a/lib/routes/esquirehk/tag.tsx +++ b/lib/routes/esquirehk/tag.tsx @@ -96,7 +96,7 @@ const renderSubpages = (subpages): string => break; } case 'video_block': { - const videoId = page.source?.split('&')[0]; + const videoId = page.source?.split('&', 1)[0]; blocks.push( `; case 'leading': case 'img': return `
`${key}="${node.attribs[key]}"`) + ? Object.entries(node.attribs) + .map(([key, value]) => `${key}="${value}"`) .join(' ') : `url="${node.url}"` }>
${ diff --git a/lib/routes/scnu/physics-school-announcements-and-news.ts b/lib/routes/scnu/physics-school-announcements-and-news.ts index aa82dcef0386..db362591f7d4 100644 --- a/lib/routes/scnu/physics-school-announcements-and-news.ts +++ b/lib/routes/scnu/physics-school-announcements-and-news.ts @@ -19,7 +19,7 @@ const getAritlces = async (category, url, cache) => { return { title: a.text(), link, - pubDate: timezone(parseDate(item.find('span.time').text()), +8), + pubDate: timezone(parseDate(item.find('span.time').text()), 8), category, }; }) @@ -39,7 +39,7 @@ const getAritlces = async (category, url, cache) => { }; const getItemsFromURLs = async (URLs, cache) => { - let items = Object.keys(URLs).map((key) => getAritlces(key, URLs[key], cache)); + let items = Object.entries(URLs).map(([key, value]) => getAritlces(key, value, cache)); items = await Promise.all(items); items = items.flat(); return items; diff --git a/lib/routes/scoop/apps.tsx b/lib/routes/scoop/apps.tsx index 5e68afbe61a3..b15d694b0c01 100644 --- a/lib/routes/scoop/apps.tsx +++ b/lib/routes/scoop/apps.tsx @@ -34,7 +34,7 @@ const filters = { export const handler = async (ctx: Context): Promise => { const { query = 's=2&d=1&n=true&dm=true&o=true' } = ctx.req.param(); - const limit: number = Number.parseInt(ctx.req.query('limit') ?? '50', 10); + const limit = Number(ctx.req.query('limit') ?? '50'); const baseUrl = 'https://scoop.sh'; const apiBaseUrl = 'https://scoopsearch.search.windows.net'; diff --git a/lib/routes/sctv/programme.tsx b/lib/routes/sctv/programme.tsx index bf8c8e4a8d41..27ffa096a362 100644 --- a/lib/routes/sctv/programme.tsx +++ b/lib/routes/sctv/programme.tsx @@ -112,7 +112,7 @@ async function handler(ctx) { guid: item.id, title: item.programmeTitle, link: item.programmeUrl, - pubDate: timezone(parseDate(item.pubTime), +8), + pubDate: timezone(parseDate(item.pubTime), 8), description: renderToString(