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 ${text.slice(0, brief)}… ${text} ${text.slice(0, brief)}… ${text} ${item.digest} 版本: {item.Version} 大小: {item.FileSize}`,
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(
')[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
))}
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{detail.label}
- {detail.value?.href && detail.value?.text ? {detail.value.text} : detail.value}
+ {detail.value?.href && detail.value.text ? {detail.value.text} : detail.value}
')
- .flatMap((line, index, array) => (lineMap[index] ? array[lineMap[index]] : line).split('
'))
+ .flatMap((line, index, array) => {
+ const mapped = lineMap[index];
+ return (mapped ? array[mapped] : line).split('
');
+ })
.slice(1, -2)
.join('
');
};
diff --git a/lib/routes/6park/index.ts b/lib/routes/6park/index.ts
index 4e2015f42835..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
- 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()}${text.word.words}
`; } } - } - if (element.para_type === 2) { + } else if (element.para_type === 2) { for (const image of element.pic.pics) { description += `
${await nextNode(node.content)}
`, text: (node) => { const { attributes: attr, value: val } = node; - if (attr?.emphasis && attr?.strong) { + if (attr?.emphasis && attr.strong) { return `${val}`; - } else if (attr?.emphasis) { + } + if (attr?.emphasis) { return `${val}`; - } else if (attr?.strong) { + } + if (attr?.strong) { return `${val}`; - } else { - return val; } + return val; }, 'inline-newsletter': async (node, nextNode) => `${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${item.brief.replaceAll('\r\n', '
')}
`, })); @@ -90,8 +90,12 @@ async function handler(ctx) { item.description += `+ 地点: + {location} +
++ 开展: + {startDate ?? '未定/常设'} +
++ 闭展: + {endDate ?? '未定/常设'} +
+ {fullDuration && ( ++ 原始展期:{fullDuration} +
+ )} +