diff --git a/.github/workflows/build-assets.yml b/.github/workflows/build-assets.yml
index 44e45a852264..1b7a88b6a84d 100644
--- a/.github/workflows/build-assets.yml
+++ b/.github/workflows/build-assets.yml
@@ -18,7 +18,7 @@ jobs:
contents: write
steps:
- name: Checkout
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Install pnpm
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Use Node.js Active LTS
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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..1b1c81be83f4 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# 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..f39a8c99dee1 100644
--- a/.github/workflows/comment-on-issue.yml
+++ b/.github/workflows/comment-on-issue.yml
@@ -26,7 +26,7 @@ jobs:
outputs:
closed: ${{ steps.check.outputs.closed }}
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
@@ -60,7 +60,7 @@ jobs:
(needs.checkIssue.result == 'success' || needs.checkIssue.result == 'skipped') &&
needs.checkIssue.outputs.closed != 'true'
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
diff --git a/.github/workflows/dependabot-fork.yml b/.github/workflows/dependabot-fork.yml
index 40494f4e0b40..48b76b399419 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- 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..d0775bcc611e 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Extract repository name
id: repo-name
@@ -277,7 +277,7 @@ jobs:
if: needs.check-env.outputs.check-docker == 'true'
timeout-minutes: 5
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- 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..48a082dd1afb 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# https://github.com/orgs/community/discussions/25220#discussioncomment-11316244
- name: Search the PR that triggered this workflow
@@ -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..e89d43cb3da0 100644
--- a/.github/workflows/docker-test.yml
+++ b/.github/workflows/docker-test.yml
@@ -27,7 +27,7 @@ jobs:
timeout-minutes: 10
steps:
- name: Checkout
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Set up Docker Buildx # needed by `cache-from`
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
@@ -75,6 +75,6 @@ jobs:
- name: Upload Docker image
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
- name: docker-image
+ archive: false
path: rsshub.tar.zst
retention-days: 1
diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml
index 776717f4fc1c..254304d1204d 100644
--- a/.github/workflows/format.yml
+++ b/.github/workflows/format.yml
@@ -14,7 +14,7 @@ jobs:
timeout-minutes: 15
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
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..9f4c99ac5481 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- id: vouch
uses: mitchellh/vouch/action/manage-by-issue@c6d80ead49839655b61b422700b7a3bc9d0804a9 # v1.4.2
@@ -102,11 +102,11 @@ jobs:
- name: Checkout
if: ${{ !github.event.issue.pull_request }}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Checkout PR
if: github.event.issue.pull_request
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ fromJson(steps.pr-data.outputs.data).head.ref }}
@@ -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..fd649a1ffcb0 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -20,7 +20,7 @@ jobs:
permissions:
security-events: write
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
@@ -80,7 +80,7 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Check if PR author is denounced
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml
index 5dcd201dc749..c81a75ce1f53 100644
--- a/.github/workflows/npm-publish.yml
+++ b/.github/workflows/npm-publish.yml
@@ -22,7 +22,7 @@ jobs:
env:
HUSKY: 0
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
diff --git a/.github/workflows/pr-review.yml b/.github/workflows/pr-review.yml
index 512b7b734d51..9fc5f78347f2 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# 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..be4068a3bdc5 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- 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..496219130867 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- 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..27efbe25c481 100644
--- a/.github/workflows/test-full-routes.yml
+++ b/.github/workflows/test-full-routes.yml
@@ -14,7 +14,7 @@ jobs:
contents: write
steps:
- name: Checkout
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Install pnpm
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Use Node.js Active LTS
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index aedd27b554ed..206076aadbed 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -27,11 +27,10 @@ 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: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
@@ -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,7 +75,7 @@ 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: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
@@ -91,7 +90,7 @@ 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
@@ -125,7 +124,7 @@ 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: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
diff --git a/.github/workflows/update-nix-hash.yml b/.github/workflows/update-nix-hash.yml
index 51f1f97812c6..01828d0303ac 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Install Nix
uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
with:
diff --git a/.oxlintrc.json b/.oxlintrc.json
index 85967256d352..619b99869f22 100644
--- a/.oxlintrc.json
+++ b/.oxlintrc.json
@@ -14,6 +14,7 @@
"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",
@@ -32,19 +33,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 +66,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,12 +149,15 @@
// #endregion
// #region --- unicorn recommended ---
+ // "unicorn-js/better-dom-traversing": "error", false postive on cheerio
"unicorn/catch-error-name": "error",
"unicorn/consistent-assert": "error",
+ "unicorn-js/consistent-compound-words": "error", // use jsPlugins
"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-json-file-read": "error", // use jsPlugins
"unicorn/consistent-template-literal-escape": "error",
"unicorn/empty-brace-spaces": "error",
"unicorn/error-message": "error",
@@ -168,22 +172,31 @@
"unicorn/no-accessor-recursion": "error",
"unicorn/no-anonymous-default-export": "error",
// "unicorn/no-array-callback-reference": "error",
+ "unicorn-js/no-array-fill-with-reference-type": "error", // use jsPlugins
"unicorn/no-array-for-each": "error",
+ "unicorn-js/no-array-from-fill": "error", // use jsPlugins
"unicorn/no-array-method-this-argument": "error",
// "unicorn/no-array-reduce": "error",
"unicorn/no-array-reverse": "error",
// "unicorn/no-array-sort": "error",
// "unicorn/no-await-expression-member": "error",
"unicorn/no-await-in-promise-methods": "error",
+ "unicorn-js/no-blob-to-file": "error", // use jsPlugins
+ "unicorn-js/no-canvas-to-image": "error", // use jsPlugins
+ "unicorn-js/no-confusing-array-splice": "error", // use jsPlugins
"unicorn/no-console-spaces": "error",
"unicorn/no-document-cookie": "error",
+ "unicorn-js/no-duplicate-set-values": "error", // use jsPlugins
// "unicorn/no-empty-file": "error",
+ "unicorn-js/no-exports-in-scripts": "error", // use jsPlugins
// "unicorn/no-for-loop": "error", // won't be implemented
// "unicorn/no-hex-escape": "error",
"unicorn/no-immediate-mutation": "error",
+ "unicorn-js/no-incorrect-query-selector": "error", // use jsPlugins
"unicorn/no-instanceof-builtins": "error",
"unicorn/no-invalid-fetch-options": "error",
"unicorn/no-invalid-remove-event-listener": "error",
+ "unicorn-js/no-late-current-target-access": "error", // use jsPlugins
"unicorn/no-lonely-if": "error",
"unicorn/no-magic-array-flat-depth": "error",
// "unicorn/no-named-default": "error", -> use import/no-named-default
@@ -201,14 +214,17 @@
"unicorn/no-static-only-class": "error",
"unicorn/no-thenable": "error",
"unicorn/no-this-assignment": "error",
+ "unicorn-js/no-this-outside-of-class": "error", // use jsPlugins
"unicorn/no-typeof-undefined": "error",
"unicorn/no-unnecessary-array-flat-depth": "error",
"unicorn/no-unnecessary-array-splice-count": "error",
"unicorn/no-unnecessary-await": "error",
+ "unicorn-js/no-unnecessary-nested-ternary": "error", // use jsPlugins
"unicorn-js/no-unnecessary-polyfills": "error", // use jsPlugins
"unicorn/no-unnecessary-slice-end": "error",
"unicorn/no-unreadable-array-destructuring": "error",
"unicorn/no-unreadable-iife": "error",
+ "unicorn-js/no-unused-array-method-return": "error", // use jsPlugins
"unicorn/no-useless-collection-argument": "error",
"unicorn/no-useless-error-capture-stack-trace": "error",
"unicorn/no-useless-fallback-in-spread": "error",
@@ -226,6 +242,7 @@
"unicorn/prefer-array-flat": "error",
"unicorn/prefer-array-flat-map": "error",
"unicorn/prefer-array-index-of": "error",
+ "unicorn-js/prefer-array-last-methods": "error", // use jsPlugins
"unicorn/prefer-array-some": "error",
"unicorn/prefer-at": "error",
"unicorn/prefer-bigint-literals": "error",
@@ -241,10 +258,15 @@
"unicorn/prefer-dom-node-text-content": "error",
"unicorn/prefer-event-target": "error",
"unicorn-js/prefer-export-from": "error", // use jsPlugins
+ "unicorn-js/prefer-get-or-insert-computed": "error", // use jsPlugins
// "unicorn/prefer-global-this": "error",
+ // "unicorn-js/prefer-https": "error",
"unicorn/prefer-includes": "error",
+ "unicorn-js/prefer-includes-over-repeated-comparisons": "error", // use jsPlugins
+ // "unicorn-js/prefer-iterator-to-array-at-end": "error",
"unicorn/prefer-keyboard-event-key": "error",
"unicorn/prefer-logical-operator-over-ternary": "error",
+ "unicorn-js/prefer-math-abs": "error", // use jsPlugins
"unicorn/prefer-math-min-max": "error",
"unicorn/prefer-math-trunc": "error",
"unicorn/prefer-modern-dom-apis": "error",
@@ -258,6 +280,7 @@
"unicorn/prefer-optional-catch-binding": "error",
"unicorn/prefer-prototype-methods": "error",
"unicorn/prefer-query-selector": "error",
+ "unicorn-js/prefer-queue-microtask": "error", // use jsPlugins
"unicorn/prefer-reflect-apply": "error",
"unicorn/prefer-regexp-test": "error",
"unicorn/prefer-response-static-json": "error",
@@ -265,8 +288,12 @@
"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-split-limit": "error", // use jsPlugins
// "unicorn/prefer-spread": "error",
+ "unicorn-js/prefer-string-match-all": "error", // use jsPlugins
+ "unicorn-js/prefer-string-pad-start-end": "error", // use jsPlugins
"unicorn/prefer-string-raw": "error",
+ "unicorn-js/prefer-string-repeat": "error", // use jsPlugins
"unicorn/prefer-string-replace-all": "error",
// "unicorn/prefer-string-slice": "error",
"unicorn/prefer-string-starts-ends-with": "error",
@@ -279,9 +306,11 @@
// "unicorn/prevent-abbreviations": "error",
"unicorn/relative-url-style": "error",
"unicorn/require-array-join-separator": "error",
+ "unicorn-js/require-css-escape": "error", // use jsPlugins
"unicorn/require-module-attributes": "error",
"unicorn/require-module-specifiers": "error",
"unicorn/require-number-to-fixed-digits-argument": "error",
+ "unicorn-js/require-passive-events": "error", // use jsPlugins
// "unicorn/switch-case-braces": "error",
"unicorn-js/switch-case-break-position": "error", // use jsPlugins
"unicorn-js/template-indent": "error", // use jsPlugins
@@ -289,12 +318,74 @@
"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
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 6c0244f8d43f..6e9b14134c6b 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,84 +1,83 @@
-# Contributor Covenant Code of Conduct
+# Contributor Covenant 3.0 Code of Conduct
## Our Pledge
-We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
+We pledge to make our community welcoming, safe, and equitable for all.
-We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
+We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or religion, national or social origin, socio-economic position, level of education, or other status. The same privileges of participation are extended to everyone who participates in good faith and in accordance with this Covenant.
-## Our Standards
+## Encouraged Behaviors
-Examples of behavior that contributes to a positive environment for our community include:
+While acknowledging differences in social norms, we all strive to meet our community's expectations for positive behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture, background, or native language.
-- Demonstrating empathy and kindness toward other people
-- Being respectful of differing opinions, viewpoints, and experiences
-- Giving and gracefully accepting constructive feedback
-- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
-- Focusing on what is best not just for us as individuals, but for the overall community
+With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared values, including:
-Examples of unacceptable behavior include:
+1. Respecting the **purpose of our community**, our activities, and our ways of gathering.
+2. Engaging **kindly and honestly** with others.
+3. Respecting **different viewpoints** and experiences.
+4. **Taking responsibility** for our actions and contributions.
+5. Gracefully giving and accepting **constructive feedback**.
+6. Committing to **repairing harm** when it occurs.
+7. Behaving in other ways that promote and sustain the **well-being of our community**.
-- The use of sexualized language or imagery, and sexual attention or
- advances of any kind
-- Trolling, insulting or derogatory comments, and personal or political attacks
-- Public or private harassment
-- Publishing others' private information, such as a physical or email
- address, without their explicit permission
-- Other conduct which could reasonably be considered inappropriate in a
- professional setting
+## Restricted Behaviors
-## Enforcement Responsibilities
+We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are violations of this Code of Conduct.
-Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
+1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any clear request to stop.
+2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of people.
+3. **Stereotyping or discrimination.** Characterizing anyone’s personality or behavior on the basis of immutable identities or traits.
+4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or purpose of the community.
+5. **Violating confidentiality**. Sharing or acting on someone's personal or private information without their permission.
+6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group.
+7. Behaving in other ways that **threaten the well-being** of our community.
-Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
+### Other Restrictions
-## Scope
-
-This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
-
-## Enforcement
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at . All complaints will be reviewed and investigated promptly and fairly.
-
-All community leaders are obligated to respect the privacy and security of the reporter of any incident.
-
-## Enforcement Guidelines
+1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade enforcement actions.
+2. **Failing to credit sources.** Not properly crediting the sources of content you contribute.
+3. **Promotional materials**. Sharing marketing or other commercial content in a way that is outside the norms of the community.
+4. **Irresponsible communication.** Failing to responsibly present content which includes, links or describes any other restricted behaviors.
-Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
+## Reporting an Issue
-### 1. Correction
+Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviors and norms that can help avoid conflicts and minimize harm.
-**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
+When an incident does occur, it is important to report it promptly. To report a possible violation, send an email to .
-**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
+Community Moderators take reports of violations seriously and will make every effort to respond in a timely manner. They will investigate all reports of code of conduct violations, reviewing messages, logs, and recordings, or interviewing witnesses and other participants. Community Moderators will keep investigation and enforcement actions as transparent as possible while prioritizing safety and confidentiality. In order to honor these values, enforcement actions are carried out in private with the involved parties, but communicating to the whole community may be part of a mutually agreed upon resolution.
-### 2. Warning
+## Addressing and Repairing Harm
-**Community Impact**: A violation through a single incident or series of actions.
+If an investigation by the Community Moderators finds that this Code of Conduct has been violated, the following enforcement ladder may be used to determine how best to repair harm, based on the incident's impact on the individuals involved and the community as a whole. Depending on the severity of a violation, lower rungs on the ladder may be skipped.
-**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
+1. Warning
+ 1. Event: A violation involving a single incident or series of incidents.
+ 2. Consequence: A private, written warning from the Community Moderators.
+ 3. Repair: Examples of repair include a private written apology, acknowledgement of responsibility, and seeking clarification on expectations.
+2. Temporarily Limited Activities
+ 1. Event: A repeated incidence of a violation that previously resulted in a warning, or the first incidence of a more serious violation.
+ 2. Consequence: A private, written warning with a time-limited cooldown period designed to underscore the seriousness of the situation and give the community members involved time to process the incident. The cooldown period may be limited to particular communication channels or interactions with particular community members.
+ 3. Repair: Examples of repair may include making an apology, using the cooldown period to reflect on actions and impact, and being thoughtful about re-entering community spaces after the period is over.
+3. Temporary Suspension
+ 1. Event: A pattern of repeated violation which the Community Moderators have tried to address with warnings, or a single serious violation.
+ 2. Consequence: A private written warning with conditions for return from suspension. In general, temporary suspensions give the person being suspended time to reflect upon their behavior and possible corrective actions.
+ 3. Repair: Examples of repair include respecting the spirit of the suspension, meeting the specified conditions for return, and being thoughtful about how to reintegrate with the community when the suspension is lifted.
+4. Permanent Ban
+ 1. Event: A pattern of repeated code of conduct violations that other steps on the ladder have failed to resolve, or a violation so serious that the Community Moderators determine there is no way to keep the community safe with this person as a member.
+ 2. Consequence: Access to all community spaces, tools, and communication channels is removed. In general, permanent bans should be rarely used, should have strong reasoning behind them, and should only be resorted to if working through other remedies has failed to change the behavior.
+ 3. Repair: There is no possible repair in cases of this severity.
-### 3. Temporary Ban
+This enforcement ladder is intended as a guideline. It does not limit the ability of Community Managers to use their discretion and judgment, in keeping with the best interests of our community.
-**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
-
-**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
-
-### 4. Permanent Ban
-
-**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
+## Scope
-**Consequence**: A permanent ban from any sort of public interaction within the project community.
+This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public or other spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Attribution
-This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
-available at .
-
-Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
+This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at [https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/).
-[homepage]: https://www.contributor-covenant.org
+Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy of this license, visit [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)
-For answers to common questions about this code of conduct, see the FAQ at
-. Translations are available at .
+For answers to common questions about Contributor Covenant, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are provided at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). Additional enforcement and community guideline resources can be found at [https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources). The enforcement ladder was inspired by the work of [Mozilla’s code of conduct team](https://github.com/mozilla/inclusion).
diff --git a/Dockerfile b/Dockerfile
index 6683193b7ed6..0654acb9915a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -40,7 +40,7 @@ WORKDIR /ver
COPY ./package.json /app/
RUN \
set -ex && \
- grep -Po '(?<="playwright": ")[^\s"]*(?=")' /app/package.json | tee /ver/.playwright_version && \
+ grep -Po '(?<="patchright": ")[^\s"]*(?=")' /app/package.json | tee /ver/.patchright_version && \
grep -Po '(?<="@vercel/nft": ")[^\s"]*(?=")' /app/package.json | tee /ver/.nft_version && \
grep -Po '(?<="fs-extra": ")[^\s"]*(?=")' /app/package.json | tee /ver/.fs_extra_version
@@ -88,12 +88,12 @@ FROM node:24-bookworm-slim AS chromium-downloader
# Yeah, downloading Chromium never needs those dependencies below.
WORKDIR /app
-COPY --from=dep-version-parser /ver/.playwright_version /app/.playwright_version
+COPY --from=dep-version-parser /ver/.patchright_version /app/.patchright_version
ARG TARGETPLATFORM
ARG USE_CHINA_NPM_REGISTRY=0
ARG PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
-# The official recommended way to use Playwright on x86(_64) is to use the bundled browser.
+# The official recommended way to use Patchright on x86(_64) is to use the bundled browser.
RUN \
set -ex ; \
if [ "$PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD" = 0 ] && [ "$TARGETPLATFORM" = 'linux/amd64' ]; then \
@@ -106,9 +106,9 @@ RUN \
unset PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD && \
export PLAYWRIGHT_BROWSERS_PATH=/app/node_modules/.cache/ms-playwright && \
corepack enable pnpm && \
- pnpm --allow-build=playwright add playwright@$(cat /app/.playwright_version) --save-prod && \
+ pnpm --allow-build=patchright --allow-build=patchright-core add patchright@$(cat /app/.patchright_version) --save-prod && \
pnpm rb && \
- pnpm exec playwright install chromium ; \
+ pnpm exec patchright install chromium ; \
else \
mkdir -p /app/node_modules/.cache/ms-playwright ; \
fi;
diff --git a/flake.lock b/flake.lock
index cef7e05c4f14..372e6dfc9d77 100644
--- a/flake.lock
+++ b/flake.lock
@@ -64,11 +64,11 @@
"rust-overlay": "rust-overlay"
},
"locked": {
- "lastModified": 1780316214,
- "narHash": "sha256-X3EG0oxt03MegwC/MnQv1saoq9nphEGSSGEAj8mZQOg=",
+ "lastModified": 1781041252,
+ "narHash": "sha256-i25K7cJKTkVWRgMLPQt2UI7E9TxyepRfkGnTT0LfxPM=",
"owner": "cachix",
"repo": "devenv",
- "rev": "d5e9138bae90fe199fbe5de7675014d76d28873b",
+ "rev": "4ec8cee2f960a44dff75b70fe7ab9b2428bc2c6e",
"type": "github"
},
"original": {
@@ -300,11 +300,11 @@
},
"nixpkgs_2": {
"locked": {
- "lastModified": 1780243769,
- "narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=",
+ "lastModified": 1780749050,
+ "narHash": "sha256-3av0pIjlOWQ6rDbNOmpUSvbNnJkGORQKKjb4LtCZsIY=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "331800de5053fcebacf6813adb5db9c9dca22a0c",
+ "rev": "a799d3e3886da994fa307f817a6bc705ae538eeb",
"type": "github"
},
"original": {
diff --git a/lib/bilibili-video-route.test.ts b/lib/bilibili-video-route.test.ts
index 09f601237bd5..39dd7ea1b034 100644
--- a/lib/bilibili-video-route.test.ts
+++ b/lib/bilibili-video-route.test.ts
@@ -11,15 +11,13 @@ const destroy = vi.fn();
const getPlaywrightPage = vi.fn();
const goto = vi.fn();
const on = vi.fn();
-const setCookie = vi.fn();
-const setRequestInterception = vi.fn();
+const pageRoute = vi.fn();
const waitForResponse = vi.fn();
const page = {
goto,
on,
- setCookie,
- setRequestInterception,
+ route: pageRoute,
waitForResponse,
};
@@ -66,8 +64,7 @@ describe('/bilibili/user/video/:uid', () => {
getPlaywrightPage.mockReset();
goto.mockReset();
on.mockReset();
- setCookie.mockReset();
- setRequestInterception.mockReset();
+ pageRoute.mockReset();
waitForResponse.mockReset();
});
@@ -109,6 +106,7 @@ describe('/bilibili/user/video/:uid', () => {
getPlaywrightPage.mockImplementation(async (_url, options) => {
await options.onBeforeLoad?.(page);
return {
+ context: {},
destroy,
page,
};
@@ -171,6 +169,7 @@ describe('/bilibili/user/video/:uid', () => {
getPlaywrightPage.mockImplementation(async (_url, options) => {
await options.onBeforeLoad?.(page);
return {
+ context: {},
destroy,
page,
};
diff --git a/lib/config.ts b/lib/config.ts
index a460f33723b0..70ab2128cf54 100644
--- a/lib/config.ts
+++ b/lib/config.ts
@@ -10,6 +10,7 @@ type ConfigEnvKeys =
| 'NODE_NAME'
| 'PLAYWRIGHT_WS_ENDPOINT'
| 'PUPPETEER_WS_ENDPOINT'
+ | 'PLAYWRIGHT_CDP_ENDPOINT'
| 'CHROMIUM_EXECUTABLE_PATH'
// Network
| 'PORT'
@@ -260,6 +261,7 @@ export type Config = {
isPackage: boolean;
nodeName?: string;
playwrightWSEndpoint?: string;
+ playwrightCDPEndpoint?: string;
chromiumExecutablePath?: string;
// network
connect: {
@@ -717,7 +719,7 @@ const toBoolean = (value: string | undefined, defaultValue: boolean) => {
if (value === undefined) {
return defaultValue;
} else {
- return value === '' || value === '0' || value === 'false' ? false : !!value;
+ return ['', '0', 'false'].includes(value) ? false : !!value;
}
};
@@ -756,6 +758,7 @@ const calculateValue = () => {
isPackage: !!envs.IS_PACKAGE,
nodeName: envs.NODE_NAME,
playwrightWSEndpoint: envs.PLAYWRIGHT_WS_ENDPOINT ?? envs.PUPPETEER_WS_ENDPOINT,
+ playwrightCDPEndpoint: envs.PLAYWRIGHT_CDP_ENDPOINT,
chromiumExecutablePath: envs.CHROMIUM_EXECUTABLE_PATH,
// network
connect: {
diff --git a/lib/errors/index.test.ts b/lib/errors/index.test.ts
index 9c102b973600..397d227cb769 100644
--- a/lib/errors/index.test.ts
+++ b/lib/errors/index.test.ts
@@ -25,7 +25,7 @@ describe('httperror', () => {
describe('RequestInProgressError', () => {
it('RequestInProgressError with retry', async () => {
const responses = await Promise.all([app.request('/test/slow'), app.request('/test/slow')]);
- expect(new Set(responses.map((r) => r.status))).toEqual(new Set([200, 200]));
+ expect(responses.map((r) => r.status)).toEqual([200, 200]);
});
it('RequestInProgressError', async () => {
const responses = await Promise.all([app.request('/test/slow4'), app.request('/test/slow4')]);
diff --git a/lib/errors/index.tsx b/lib/errors/index.tsx
index b78603815c00..2aaf4c4126b5 100644
--- a/lib/errors/index.tsx
+++ b/lib/errors/index.tsx
@@ -39,13 +39,13 @@ export const errorHandler: ErrorHandler = (error, ctx) => {
if (config.honeybadger.apiKey) {
Honeybadger.notify(error, {
- context: { name: requestPath.split('/')[1] },
+ context: { name: requestPath.split('/', 2)[1] },
});
}
if (config.sentry.dsn) {
Sentry.withScope((scope) => {
- scope.setTag('name', requestPath.split('/')[1]);
+ scope.setTag('name', requestPath.split('/', 2)[1]);
Sentry.captureException(error);
});
}
diff --git a/lib/middleware/access-control.ts b/lib/middleware/access-control.ts
index 01cbbd5f7b8c..969308227698 100644
--- a/lib/middleware/access-control.ts
+++ b/lib/middleware/access-control.ts
@@ -13,7 +13,7 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
const accessKey = ctx.req.query('key');
const accessCode = ctx.req.query('code');
- if (requestPath === '/' || requestPath === '/robots.txt' || requestPath === '/favicon.ico' || requestPath === '/logo.png') {
+ if (['/', '/robots.txt', '/favicon.ico', '/logo.png'].includes(requestPath)) {
await next();
} else {
if (config.accessKey && !(config.accessKey === accessKey || accessCode === md5(requestPath + config.accessKey))) {
diff --git a/lib/middleware/anti-hotlink.ts b/lib/middleware/anti-hotlink.ts
index 49a849570b04..7dd6e897661d 100644
--- a/lib/middleware/anti-hotlink.ts
+++ b/lib/middleware/anti-hotlink.ts
@@ -6,7 +6,7 @@ import { config } from '@/config';
import type { Data } from '@/types';
import logger from '@/utils/logger';
-const templateRegex = /\${([^{}]+)}/g;
+const templateRegex = /\$\{([^{}]+)\}/g;
const allowedUrlProperties = new Set(['hash', 'host', 'hostname', 'href', 'origin', 'password', 'pathname', 'port', 'protocol', 'search', 'searchParams', 'username']);
// match path or sub-path
@@ -150,7 +150,7 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
if (item.enclosure_url && item.enclosure_type) {
if (item.enclosure_type.startsWith('image/')) {
item.enclosure_url = replaceUrl(imageHotlinkTemplate, item.enclosure_url);
- } else if (/^(video|audio)\//.test(item.enclosure_type)) {
+ } else if (/^(?:video|audio)\//.test(item.enclosure_type)) {
item.enclosure_url = replaceUrl(multimediaHotlinkTemplate, item.enclosure_url);
}
}
diff --git a/lib/middleware/template.tsx b/lib/middleware/template.tsx
index d9f5ed98655c..78673a9504a5 100644
--- a/lib/middleware/template.tsx
+++ b/lib/middleware/template.tsx
@@ -28,7 +28,7 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
return ctx.json(ctx.get('json') || { message: 'plugin does not set debug json' });
}
- if (/(\d+)\.debug\.html$/.test(outputType)) {
+ if (/\d+\.debug\.html$/.test(outputType)) {
const index = Number.parseInt(outputType.match(/(\d+)\.debug\.html$/)?.[1] || '0');
return ctx.html(data?.item?.[index]?.description || `data.item[${index}].description not found`);
}
@@ -58,7 +58,8 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
// https://stackoverflow.com/questions/1497885/remove-control-characters-from-php-string/1497928#1497928
// remove unicode control characters
// see #14940 #14943 #15262
- item.description = item.description.replaceAll(/[\u0000-\u0009\u000B\u000C\u000E-\u001F\u007F\u200B\uFFFF]/g, '');
+ // oxlint-disable-next-line no-control-regex
+ item.description = item.description.replaceAll(/[\u0000-\u0009\v\f\u000E-\u001F\u007F\u200B\uFFFF]/g, '');
}
if (typeof item.author === 'string') {
diff --git a/lib/registry.ts b/lib/registry.ts
index 414dfb5d15db..e07ca28d3d80 100644
--- a/lib/registry.ts
+++ b/lib/registry.ts
@@ -95,7 +95,7 @@ if (Object.keys(modules).length) {
| {
apiRoute: APIRoute;
};
- const namespace = module.split(/[/\\]/)[1];
+ const namespace = module.split(/[/\\]/, 2)[1];
if ('namespace' in content) {
namespaces[namespace] = Object.assign(
{
diff --git a/lib/routes/005/index.tsx b/lib/routes/005/index.tsx
index 43c2be4b5b92..f0b047e26289 100644
--- a/lib/routes/005/index.tsx
+++ b/lib/routes/005/index.tsx
@@ -85,7 +85,7 @@ export const handler = async (ctx) => {
return {
title,
- description: title.split(/_/)[0],
+ description: title.split(/_/, 1)[0],
link: currentUrl,
item: items,
allowEmpty: true,
diff --git a/lib/routes/12371/zxfb.ts b/lib/routes/12371/zxfb.ts
index e5049220597f..b6b521b9254a 100644
--- a/lib/routes/12371/zxfb.ts
+++ b/lib/routes/12371/zxfb.ts
@@ -16,7 +16,7 @@ const handler = async (ctx) => {
const $ = cheerio.load(response.data);
- const pattern = /item=(\[{.*?}]);/;
+ const pattern = /item=(\[\{.*?\}\]);/;
const newsList = JSON.parse($('script[language="javascript"]').text().match(pattern)?.[1].replaceAll("'", '"') || '[]');
const topNewsList = newsList.slice(0, limit).map((item) => ({
diff --git a/lib/routes/141jav/index.tsx b/lib/routes/141jav/index.tsx
index 350aa2b25cce..25c600946263 100644
--- a/lib/routes/141jav/index.tsx
+++ b/lib/routes/141jav/index.tsx
@@ -104,7 +104,7 @@ async function handler(ctx) {
});
return {
- title: `141JAV - ${$('title').text().split('-')[0].trim()}`,
+ title: `141JAV - ${$('title').text().split('-', 1)[0].trim()}`,
link: currentUrl,
item: items,
};
diff --git a/lib/routes/141ppv/index.tsx b/lib/routes/141ppv/index.tsx
index 4d27b39b20c3..ee85fd0eaa32 100644
--- a/lib/routes/141ppv/index.tsx
+++ b/lib/routes/141ppv/index.tsx
@@ -163,7 +163,7 @@ async function handler(ctx) {
});
return {
- title: `141PPV - ${$('title').text().split('-')[0].trim()}`,
+ title: `141PPV - ${$('title').text().split('-', 1)[0].trim()}`,
link: currentUrl,
item: items,
};
diff --git a/lib/routes/163/news/rank.ts b/lib/routes/163/news/rank.ts
index d7643a8d3aa0..2ee9d0bfed7c 100644
--- a/lib/routes/163/news/rank.ts
+++ b/lib/routes/163/news/rank.ts
@@ -152,8 +152,8 @@ async function handler(ctx) {
cache.tryGet(item.link, async () => {
try {
let link;
- if (category === 'auto' || category === 'house' || category === 'travel') {
- const category = item.link.split('.163.com')[0].split('//').pop().split('.').pop();
+ if (['auto', 'house', 'travel'].includes(category)) {
+ const category = item.link.split('.163.com', 1)[0].split('//').pop().split('.').pop();
link = `https://3g.163.com/${category}/article/${item.link.split('/').pop()}`;
} else {
const pathname = new URL(item.link).pathname;
diff --git a/lib/routes/163/news/special.ts b/lib/routes/163/news/special.ts
index ea7d1948c278..86f93caba8df 100644
--- a/lib/routes/163/news/special.ts
+++ b/lib/routes/163/news/special.ts
@@ -102,7 +102,7 @@ async function handler(ctx) {
const url = `https://3g.163.com/touch/reconstruct/article/list/${type}/0-20.html`;
const response = await got(url);
const data = response.data;
- const matches = data.replaceAll(/\s/g, '').match(/artiList\((.*?)]}\)/);
+ const matches = data.replaceAll(/\s/g, '').match(/artiList\((.*?)\]\}\)/);
const articlelist0 = matches[1].replace(/".*?wangning/, '"articles') + ']}';
const articlelist = JSON.parse(articlelist0);
const articles = articlelist.articles;
@@ -112,7 +112,7 @@ async function handler(ctx) {
let url = article.url;
if (url === null || article.skipType === 'video') {
const skipurl = article.skipURL;
- const vid = skipurl.match(/vid=(.*?)$/);
+ const vid = skipurl.match(/vid=(.*)$/);
if (vid !== null) {
url = `https://3g.163.com/exclusive/video/${vid[1]}.html`;
}
diff --git a/lib/routes/163/open/vip.tsx b/lib/routes/163/open/vip.tsx
index 4ee699dbc0dd..82784e98fb50 100644
--- a/lib/routes/163/open/vip.tsx
+++ b/lib/routes/163/open/vip.tsx
@@ -65,7 +65,7 @@ async function handler() {
const initialState = JSON.parse(
$('script')
.text()
- .match(/window\.__INITIAL_STATE__=(.*);\(function\(\){var/)[1]
+ .match(/window\.__INITIAL_STATE__=(.*);\(function\(\)\{var/)[1]
);
const list = Object.values(initialState.courseindex.myModules).flatMap((mod) =>
@@ -93,7 +93,7 @@ async function handler() {
const $ = load(data.courseInfo.description, null, false);
$('img').each((_, img) => {
- img.attribs.src = img.attribs.src.split('?')[0];
+ img.attribs.src = img.attribs.src.split('?', 1)[0];
delete img.attribs.width;
});
diff --git a/lib/routes/163/renjian.ts b/lib/routes/163/renjian.ts
index c1f92c510a8d..d3d066ac67f1 100644
--- a/lib/routes/163/renjian.ts
+++ b/lib/routes/163/renjian.ts
@@ -91,7 +91,7 @@ async function handler(ctx) {
.text()
.match(/renjian_author = '(.*)'/)[1];
item.description = content('#endText').html() ?? content('#content').html();
- item.pubDate = timezone(parseDate(content('.pub_time').text() ?? content('.post_info').text().split('来源:')[0].trim()), 8);
+ item.pubDate = timezone(parseDate(content('.pub_time').text() ?? content('.post_info').text().split('来源:', 1)[0].trim()), 8);
return item;
})
diff --git a/lib/routes/19lou/index.ts b/lib/routes/19lou/index.ts
index 579552d48eb4..b9a90cdf74e5 100644
--- a/lib/routes/19lou/index.ts
+++ b/lib/routes/19lou/index.ts
@@ -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..48bf45dc52ed 100644
--- a/lib/routes/1lou/index.ts
+++ b/lib/routes/1lou/index.ts
@@ -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/2048/index.tsx b/lib/routes/2048/index.tsx
index 46c1e53563c2..4cf967857835 100644
--- a/lib/routes/2048/index.tsx
+++ b/lib/routes/2048/index.tsx
@@ -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/36kr/hot-list.ts b/lib/routes/36kr/hot-list.ts
index c28b4f287e81..61825e4e6b3e 100644
--- a/lib/routes/36kr/hot-list.ts
+++ b/lib/routes/36kr/hot-list.ts
@@ -79,7 +79,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)
diff --git a/lib/routes/36kr/index.ts b/lib/routes/36kr/index.ts
index 6c24a1d83726..d5790121a9a2 100644
--- a/lib/routes/36kr/index.ts
+++ b/lib/routes/36kr/index.ts
@@ -48,7 +48,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 +64,12 @@ async function handler(ctx) {
};
});
- if (!/^\/(search|newsflashes)/.test(path)) {
+ if (!/^\/(?:search|newsflashes)/.test(path)) {
items = await Promise.all(items.map((item) => ProcessItem(item, cache.tryGet)));
}
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..e20b6b7e6cc0 100644
--- a/lib/routes/36kr/utils.ts
+++ b/lib/routes/36kr/utils.ts
@@ -11,7 +11,7 @@ export const ProcessItem = (item, tryGet) =>
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..9904396f059a 100644
--- a/lib/routes/3dmgame/game.ts
+++ b/lib/routes/3dmgame/game.ts
@@ -43,7 +43,7 @@ async function handler(ctx) {
const items = await Promise.all(list.map((item) => parseArticle(item, cache.tryGet)));
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..09ea2c102724 100644
--- a/lib/routes/3dmgame/news-center.ts
+++ b/lib/routes/3dmgame/news-center.ts
@@ -64,7 +64,7 @@ async function handler(ctx) {
const out = await Promise.all(list.map((item) => parseArticle(item, cache.tryGet)));
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..e714f989c81b 100644
--- a/lib/routes/3dmgame/utils.ts
+++ b/lib/routes/3dmgame/utils.ts
@@ -11,7 +11,7 @@ const parseArticle = (item, tryGet) =>
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/423down/index.ts b/lib/routes/423down/index.ts
index c5fb36e757ac..2149c163e501 100644
--- a/lib/routes/423down/index.ts
+++ b/lib/routes/423down/index.ts
@@ -83,7 +83,7 @@ export const handler = async (ctx) => {
item.title = title;
item.description = description;
- item.pubDate = parseDate($$('p.meta-info').contents().first().text().trim().split(/\s/)[0], 'YYYY-MM-DD');
+ item.pubDate = parseDate($$('p.meta-info').contents().first().text().trim().split(/\s/, 1)[0], 'YYYY-MM-DD');
item.category = $$('p.meta-info a[rel="category tag"]')
.toArray()
.map((c) => $$(c).text());
diff --git a/lib/routes/4chan/utils.tsx b/lib/routes/4chan/utils.tsx
index 726523378e9e..f961ef8ecf16 100644
--- a/lib/routes/4chan/utils.tsx
+++ b/lib/routes/4chan/utils.tsx
@@ -46,7 +46,7 @@ const processCatalog = ({ data, board, viewOptions }: { data: CatalogApiReturn;
description: renderToString(renderPost({ post: thread, board, viewOptions })),
link: `https://boards.4chan.org/${board}/thread/${thread.no}`,
pubDate: parseDate(thread.time * 1000),
- title: thread.sub ?? sanitizeHtml(thread.com?.split('
')[0] ?? '', { allowedTags: [] }),
+ title: thread.sub ?? sanitizeHtml(thread.com?.split('
', 1)[0] ?? '', { allowedTags: [] }),
}));
};
diff --git a/lib/routes/4ksj/forum.tsx b/lib/routes/4ksj/forum.tsx
index 4762c3e87bb8..91c10204a7dd 100644
--- a/lib/routes/4ksj/forum.tsx
+++ b/lib/routes/4ksj/forum.tsx
@@ -114,7 +114,7 @@ async function handler(ctx) {
const scriptUrl = new URL(scriptPath, rootUrl).href;
const scriptResponse = await ofetch(scriptUrl);
- const key = scriptResponse.match(/{var key="(.*?)"/)?.[1];
+ const key = scriptResponse.match(/\{var key="(.*?)"/)?.[1];
const value = scriptResponse.match(/",value="(.*?)"/)?.[1];
const getPath = scriptResponse.match(/\.get\("(.*?&key=)"/)?.[1];
@@ -125,7 +125,7 @@ async function handler(ctx) {
const cookieResponse = await ofetch.raw(`${rootUrl}${getPath}${key}&value=${md5(stringtoHex(value))}`);
return cookieResponse.headers
.getSetCookie()
- .map((c) => c.split(';')[0])
+ .map((c) => c.split(';', 1)[0])
.join('; ');
});
diff --git a/lib/routes/51cto/recommend.ts b/lib/routes/51cto/recommend.ts
index 1a5c21871351..e4032bcc06cc 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);
diff --git a/lib/routes/56kog/top.ts b/lib/routes/56kog/top.ts
index f9176fd65542..55c8856a4da9 100644
--- a/lib/routes/56kog/top.ts
+++ b/lib/routes/56kog/top.ts
@@ -28,7 +28,7 @@ async function handler(ctx) {
const { category = 'weekvisit' } = ctx.req.param();
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30;
- const currentUrl = new URL(`top/${category.split(/_/)[0]}_1.html`, rootUrl).href;
+ const currentUrl = new URL(`top/${category.split(/_/, 1)[0]}_1.html`, rootUrl).href;
return await fetchItems(limit, currentUrl, cache.tryGet);
}
diff --git a/lib/routes/56kog/util.tsx b/lib/routes/56kog/util.tsx
index 0748d86f1bcc..445205f2a730 100644
--- a/lib/routes/56kog/util.tsx
+++ b/lib/routes/56kog/util.tsx
@@ -46,7 +46,7 @@ const fetchItems = async (limit, currentUrl, tryGet) => {
const as = detail.find('a');
return {
- label: detail.find('span.c-l-depths').text().split(/:/)[0],
+ label: detail.find('span.c-l-depths').text().split(/:/, 1)[0],
value:
as.length === 0
? content(
diff --git a/lib/routes/69shu/article.ts b/lib/routes/69shu/article.ts
index bc13b6c30ce7..39ca17180054 100644
--- a/lib/routes/69shu/article.ts
+++ b/lib/routes/69shu/article.ts
@@ -54,8 +54,8 @@ const createItem = (url: string) =>
cache.tryGet(url, async () => {
const html = await get(url);
const $ = load(html);
- const { articleid, chapterid, chaptername } = parseObject(/bookinfo\s?=\s?{[\S\s]+?}/, $('head>script:not([src])').text());
- const decryptionMap = parseObject(/_\d+\s?=\s?{[\S\s]+?}/, $('.txtnav+script').text());
+ const { articleid, chapterid, chaptername } = parseObject(/bookinfo\s?=\s?\{[\s\S]+?\}/, $('head>script:not([src])').text());
+ const decryptionMap = parseObject(/_\d+\s?=\s?\{[\s\S]+?\}/, $('.txtnav+script').text());
return {
title: chaptername,
@@ -70,7 +70,7 @@ const parseObject = (reg: RegExp, str: string): Record => {
const obj = {};
const match = reg.exec(str);
if (match) {
- for (const line of match[0].matchAll(/(\w+):\s?["']?([\S\s]+?)["']?[\n,}]/g)) {
+ for (const line of match[0].matchAll(/(\w+):\s?["']?([\s\S]+?)["']?[\n,}]/g)) {
obj[line[1]] = line[2];
}
}
diff --git a/lib/routes/6park/index.ts b/lib/routes/6park/index.ts
index 4e2015f42835..e3904f8f9342 100644
--- a/lib/routes/6park/index.ts
+++ b/lib/routes/6park/index.ts
@@ -66,7 +66,7 @@ async function handler(ctx) {
const content = load(detailResponse.data);
item.title = content('title').text().replace(' -6park.com', '');
- item.author = detailResponse.data.match(/送交者: .*>(.*)<.*\[/)[1];
+ item.author = detailResponse.data.match(/送交者:[^>]*>([^<]*)<\/a>/)[1].trim();
item.pubDate = timezone(parseDate(detailResponse.data.match(/于 (.*) 已读/)[1], 'YYYY-MM-DD h:m'), +8);
item.description = content('pre')
.html()
diff --git a/lib/routes/6park/news.ts b/lib/routes/6park/news.ts
index 2050185e5d8e..30941bde8440 100644
--- a/lib/routes/6park/news.ts
+++ b/lib/routes/6park/news.ts
@@ -76,7 +76,7 @@ async function handler(ctx) {
const content = load(detailResponse.data);
- const matches = detailResponse.data.match(/新闻来源:(.*?)于.*(\d{4}(?:-\d{2}){2} (?:\d{1,2}:){2}\d{1,2})/);
+ const matches = detailResponse.data.match(/新闻来源:([^于]*)于.*(\d{4}(?:-\d{2}){2} (?:\d{1,2}:){2}\d{1,2})/);
item.title = content('h2').text();
item.author = matches[1].trim();
diff --git a/lib/routes/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/utils.ts b/lib/routes/9to5/utils.ts
index 3cbb329b1fc7..f4c39a2a89ab 100644
--- a/lib/routes/9to5/utils.ts
+++ b/lib/routes/9to5/utils.ts
@@ -22,7 +22,7 @@ const ProcessFeed = (data) => {
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)) {
+ if (/\w{10}\s\w{10}/.test(classes)) {
$(e).remove();
}
}
diff --git a/lib/routes/abc/index.ts b/lib/routes/abc/index.ts
index a4eea519884e..48d3e5dac62b 100644
--- a/lib/routes/abc/index.ts
+++ b/lib/routes/abc/index.ts
@@ -49,7 +49,7 @@ async function handler(ctx) {
const feedUrl = new URL(`news/feed/${documentId}/rss.xml`, rootUrl).href;
const feedResponse = await ofetch(feedUrl);
- currentUrl = feedResponse.match(/([\w-./:?]+)<\/link>/)[1];
+ currentUrl = feedResponse.match(/([\w./:?-]+)<\/link>/)[1];
}
const currentResponse = await ofetch(currentUrl);
@@ -73,7 +73,7 @@ async function handler(ctx) {
description: renderDescription({
image: i.image
? {
- src: i.image.imgSrc.split(/\?/)[0],
+ src: i.image.imgSrc.split(/\?/, 1)[0],
alt: i.image.alt,
}
: undefined,
@@ -86,7 +86,7 @@ async function handler(ctx) {
if (i.mediaIndicator) {
item.enclosure_type = 'audio/mpeg';
- item.itunes_item_image = i.image?.imgSrc.split(/\?/)[0] ?? undefined;
+ item.itunes_item_image = i.image?.imgSrc.split(/\?/, 1)[0] ?? undefined;
item.itunes_duration = i.mediaIndicator.duration;
}
@@ -111,7 +111,7 @@ async function handler(ctx) {
element.replaceWith(
renderDescription({
image: {
- src: element.find('img').prop('src').split(/\?/)[0],
+ src: element.find('img').prop('src').split(/\?/, 1)[0],
alt: element.find('figcaption').text().trim(),
},
})
@@ -124,7 +124,7 @@ 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'));
@@ -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/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/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/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/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/aip/journal-pupp.ts b/lib/routes/aip/journal-pupp.ts
index 845661a89223..3e0412b5a127 100644
--- a/lib/routes/aip/journal-pupp.ts
+++ b/lib/routes/aip/journal-pupp.ts
@@ -18,12 +18,12 @@ const handler = async (ctx) => {
}
// use Playwright due to the obstacle by cloudflare challenge
- const browser = await playwright();
+ const context = await playwright();
const { jrnlName, list } = await cache.tryGet(
jrnlUrl,
async () => {
- const response = await playwrightGet(jrnlUrl, browser);
+ const response = await playwrightGet(jrnlUrl, context);
const $ = load(response);
const jrnlName = $('.header-journal-title').text();
const list = $('.card')
@@ -52,7 +52,7 @@ const handler = async (ctx) => {
false
);
- await browser.close();
+ await context.close();
return {
title: jrnlName,
diff --git a/lib/routes/aip/journal.ts b/lib/routes/aip/journal.ts
index 68bea00e2867..f5c718e57e89 100644
--- a/lib/routes/aip/journal.ts
+++ b/lib/routes/aip/journal.ts
@@ -43,7 +43,7 @@ async function handler(ctx) {
const $ = load(response);
const jrnlName = $('meta[property="og:title"]')
.attr('content')
- .match(/(?:[^=]*=)?\s*([^>]+)\s*/)[1];
+ .match(/(?:[^=]*=)?\s*([^>]+)/)[1];
const publication = $('.al-article-item-wrap.al-normal');
const list = publication.toArray().map((item) => {
diff --git a/lib/routes/aip/utils.tsx b/lib/routes/aip/utils.tsx
index ba592557f627..00826b987be3 100644
--- a/lib/routes/aip/utils.tsx
+++ b/lib/routes/aip/utils.tsx
@@ -1,11 +1,11 @@
import { renderToString } from 'hono/jsx/dom/server';
-const playwrightGet = async (url, browser) => {
- const page = await browser.newPage();
+const playwrightGet = async (url, context) => {
+ const page = await context.newPage();
// await page.setExtraHTTPHeaders({ referer: host });
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' ? request.continue() : request.abort();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' ? route.continue() : route.abort();
});
await page.goto(url, {
waitUntil: 'domcontentloaded',
diff --git a/lib/routes/aisixiang/column.ts b/lib/routes/aisixiang/column.ts
index ba0d9f5363dd..25e762a3922f 100644
--- a/lib/routes/aisixiang/column.ts
+++ b/lib/routes/aisixiang/column.ts
@@ -49,7 +49,7 @@ async function handler(ctx) {
return {
title: a.text(),
link: new URL(a.prop('href'), rootUrl).href,
- author: a.text().split(':')[0],
+ author: a.text().split(':', 1)[0],
pubDate: timezone(parseDate(item.find('span').text()), +8),
};
});
diff --git a/lib/routes/aisixiang/toplist.ts b/lib/routes/aisixiang/toplist.ts
index 3599fe035e94..d30422812cd0 100644
--- a/lib/routes/aisixiang/toplist.ts
+++ b/lib/routes/aisixiang/toplist.ts
@@ -27,7 +27,7 @@ async function handler(ctx) {
const $ = load(response);
- const title = `${$('a.hl').text() || ''}${$('title').text().split('_')[0]}`;
+ const title = `${$('a.hl').text() || ''}${$('title').text().split('_', 1)[0]}`;
const items = $('div.tops_list')
.slice(0, limit)
diff --git a/lib/routes/aisixiang/zhuanti.ts b/lib/routes/aisixiang/zhuanti.ts
index d6a9741212bf..c1b3f52e2604 100644
--- a/lib/routes/aisixiang/zhuanti.ts
+++ b/lib/routes/aisixiang/zhuanti.ts
@@ -52,7 +52,7 @@ async function handler(ctx) {
return {
title: a.text(),
link: new URL(a.prop('href'), rootUrl).href,
- author: a.text().split(':')[0],
+ author: a.text().split(':', 1)[0],
pubDate: timezone(parseDate(item.find('span').text()), +8),
};
});
diff --git a/lib/routes/ali213/news.ts b/lib/routes/ali213/news.ts
index f19844e862d4..93452c85a2ca 100644
--- a/lib/routes/ali213/news.ts
+++ b/lib/routes/ali213/news.ts
@@ -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/alternativeto/utils.ts b/lib/routes/alternativeto/utils.ts
index 08fd66814fef..8160f19c5471 100644
--- a/lib/routes/alternativeto/utils.ts
+++ b/lib/routes/alternativeto/utils.ts
@@ -4,17 +4,17 @@ const baseURL = 'https://alternativeto.net';
const playwrightGet = (url, cache) =>
cache.tryGet(url, async () => {
- const browser = await playwright();
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' ? request.continue() : request.abort();
+ const context = await playwright();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' ? route.continue() : route.abort();
});
await page.goto(url, {
waitUntil: 'domcontentloaded',
});
const html = await page.evaluate(() => document.documentElement.innerHTML);
- await browser.close();
+ await context.close();
return html;
});
diff --git a/lib/routes/altotrain/news.ts b/lib/routes/altotrain/news.ts
index f541b8ed2bb3..8bfbe028df29 100644
--- a/lib/routes/altotrain/news.ts
+++ b/lib/routes/altotrain/news.ts
@@ -71,7 +71,7 @@ function extractItem(a: Cheerio, language: string) {
const descEl = a.find('p').first();
const description = descEl.text().trim();
- const dateMatch = language === 'fr' ? description.match(/(\d{1,2} [a-zéû]+[.]? \d{4})/i) : description.match(/([A-Z][a-z]+[.]? \d{1,2}, \d{4})/);
+ const dateMatch = language === 'fr' ? description.match(/(\d{1,2} [a-zéû]+\.? \d{4})/i) : description.match(/([A-Z][a-z]+\.? \d{1,2}, \d{4})/);
const pubDateStr = dateMatch ? dateMatch[1].trim() : '';
const pubDate = parseDate(pubDateStr);
diff --git a/lib/routes/annualreviews/index.ts b/lib/routes/annualreviews/index.ts
index 1abce26b925f..b4a223bf6bd7 100644
--- a/lib/routes/annualreviews/index.ts
+++ b/lib/routes/annualreviews/index.ts
@@ -59,7 +59,7 @@ async function handler(ctx) {
doi,
guid: doi,
title: item.find('title').text(),
- link: item.find('link').attr('href').split('?')[0],
+ link: item.find('link').attr('href').split('?', 1)[0],
description: item.find('content').text(),
pubDate: parseDate(item.find('published').text()),
author: item
diff --git a/lib/routes/anthropic/research.ts b/lib/routes/anthropic/research.ts
index cfba01932e4d..c19564ebf810 100644
--- a/lib/routes/anthropic/research.ts
+++ b/lib/routes/anthropic/research.ts
@@ -47,7 +47,7 @@ async function handler() {
}
}
- const partRegex = /^([0-9a-zA-Z]+):([0-9a-zA-Z]+)?(\[.*)$/;
+ const partRegex = /^([0-9a-z]+):([0-9a-z]+)?(\[.*)$/i;
const fd = textList
.join('')
.split('\n')
diff --git a/lib/routes/anytxt/release-notes.ts b/lib/routes/anytxt/release-notes.ts
index 32901623dfa9..228715c4a770 100644
--- a/lib/routes/anytxt/release-notes.ts
+++ b/lib/routes/anytxt/release-notes.ts
@@ -28,7 +28,7 @@ export const handler = async (ctx: Context): Promise => {
const title: string = $el.text();
const description: string | undefined = $el.next().html() ?? '';
- const pubDateStr: string | undefined = title.split(/\s/)[0];
+ const pubDateStr: string | undefined = title.split(/\s/, 1)[0];
const linkUrl: string | undefined = targetUrl;
const upDatedStr: string | undefined = pubDateStr;
diff --git a/lib/routes/apkpure/versions.ts b/lib/routes/apkpure/versions.ts
index 911f05d2ae65..bce6bb781d1e 100644
--- a/lib/routes/apkpure/versions.ts
+++ b/lib/routes/apkpure/versions.ts
@@ -28,11 +28,11 @@ async function handler(ctx) {
const baseUrl = 'https://apkpure.com';
const link = `${baseUrl}/${region}/${pkg}/versions`;
- const browser = await playwright();
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' ? request.continue() : request.abort();
+ const context = await playwright();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' ? route.continue() : route.abort();
});
logger.http(`Requesting ${link}`);
await page.goto(link, {
@@ -40,7 +40,7 @@ async function handler(ctx) {
});
const r = await page.evaluate(() => document.documentElement.innerHTML);
- await browser.close();
+ await context.close();
const $ = load(r);
const img = new URL($('.ver-top img').attr('src'));
diff --git a/lib/routes/apnews/mobile-api.ts b/lib/routes/apnews/mobile-api.ts
index 3c894ca3a36e..ac182d98c7bc 100644
--- a/lib/routes/apnews/mobile-api.ts
+++ b/lib/routes/apnews/mobile-api.ts
@@ -10,12 +10,12 @@ import { fetchArticle } from './utils';
export const route: Route = {
path: '/mobile/:path{.+}?',
categories: ['traditional-media'],
- example: '/apnews/mobile/ap-top-news',
+ example: '/apnews/mobile',
view: ViewType.Articles,
parameters: {
path: {
description: 'Corresponding path from AP News website',
- default: 'ap-top-news',
+ default: '/',
},
},
features: {
@@ -37,7 +37,7 @@ export const route: Route = {
};
async function handler(ctx) {
- const path = ctx.req.param('path') ? `/${ctx.req.param('path')}` : '/hub/ap-top-news';
+ const path = ctx.req.param('path') ? `/${ctx.req.param('path')}` : '/';
const apiRootUrl = 'https://apnews.com/graphql/delivery/ap/v1';
const res = await ofetch(apiRootUrl, {
query: {
diff --git a/lib/routes/apnews/utils.ts b/lib/routes/apnews/utils.ts
index 083c19fb9aa7..6ccca1098359 100644
--- a/lib/routes/apnews/utils.ts
+++ b/lib/routes/apnews/utils.ts
@@ -45,7 +45,7 @@ export function fetchArticle(item) {
description: $('div.RichTextStoryBody').html() || $(':is(.VideoLead, .VideoPage-pageSubHeading)').html(),
category: [...(section ? [section] : []), ...(ldjson.keywords ?? [])],
guid: $("meta[name='brightspot.contentId']").attr('content'),
- author: ldjson.author?.map((e) => e.mainEntity),
+ author: ldjson.author,
};
} else {
// Live
diff --git a/lib/routes/app-center/release.tsx b/lib/routes/app-center/release.tsx
index 65a235222a87..a41da5c3f164 100644
--- a/lib/routes/app-center/release.tsx
+++ b/lib/routes/app-center/release.tsx
@@ -158,7 +158,7 @@ async function handler(ctx) {
const releaseResponse = await got(item.link);
const releaseInfo = releaseResponse.data;
- const userName = releaseInfo.owner.display_name;
+ const username = releaseInfo.owner.display_name;
const appOS = releaseInfo.app_os;
const shortVersion = releaseInfo.short_version; // will be an empty string for Windows
const versionCode = releaseInfo.version;
@@ -181,7 +181,7 @@ async function handler(ctx) {
const appName = releaseInfo.app_display_name;
const distributionGroupId = releaseInfo.distribution_group_id;
const distributionGroupName = releaseInfo.distribution_groups.find((group) => group.id === distributionGroupId).display_name;
- item._feed_title = `${appName} (${distributionGroupName}) for ${appOS} by ${userName} - App Center Releases`;
+ item._feed_title = `${appName} (${distributionGroupName}) for ${appOS} by ${username} - App Center Releases`;
item._feed_icon = releaseInfo.app_icon_url;
const version = shortVersion && versionCode ? `${shortVersion} (${versionCode})` : shortVersion || versionCode;
@@ -193,7 +193,7 @@ async function handler(ctx) {
(isExternalBuild ? '[External Build]' : '') +
`Version ${version}`;
item.link = link; // replace the link with the release page
- item.author = userName;
+ item.author = username;
item.description = renderDescription({
releaseDate,
sizeInMBytes,
diff --git a/lib/routes/aqara/news.ts b/lib/routes/aqara/news.ts
index 898ea0dac62d..2356ac679fcd 100644
--- a/lib/routes/aqara/news.ts
+++ b/lib/routes/aqara/news.ts
@@ -23,7 +23,7 @@ async function handler(ctx) {
const $ = load(response);
let items = response
- .match(/(parm\.newsTitle[\S\s]*?arr\.push\(parm\))/g)
+ .match(/(parm\.newsTitle[\s\S]*?arr\.push\(parm\))/g)
.slice(0, limit)
.map((item) => ({
title: item.match(/parm\.newsTitle = '(.*?)'/)[1],
@@ -47,7 +47,7 @@ async function handler(ctx) {
)
);
- const icon = $('link[rel="shortcut icon"]').prop('href').split('?')[0];
+ const icon = $('link[rel="shortcut icon"]').prop('href').split('?', 1)[0];
return {
item: items,
diff --git a/lib/routes/arcteryx/regear-new-arrivals.tsx b/lib/routes/arcteryx/regear-new-arrivals.tsx
index 6785aa96c410..7394abf81ec0 100644
--- a/lib/routes/arcteryx/regear-new-arrivals.tsx
+++ b/lib/routes/arcteryx/regear-new-arrivals.tsx
@@ -42,7 +42,7 @@ async function handler() {
const data = response.data;
const $ = load(data);
const contents = $('script:contains("window.__PRELOADED_STATE__")').text();
- const regex = /{.*}/;
+ const regex = /\{.*\}/;
let items = JSON.parse(contents.match(regex)[0]).shop.items;
items = items.filter((item) => item.availableSizes.length !== 0);
diff --git a/lib/routes/artstation/user.ts b/lib/routes/artstation/user.ts
index 49ef9bd275bd..bd088e835b67 100644
--- a/lib/routes/artstation/user.ts
+++ b/lib/routes/artstation/user.ts
@@ -45,7 +45,7 @@ async function handler(ctx) {
method: 'POST',
headers,
});
- return tokenResponse.headers.getSetCookie()[0].split(';')[0].split('=')[1];
+ return tokenResponse.headers.getSetCookie()[0].split(';', 1)[0].split('=', 2)[1];
});
const { data: userData } = await got(`https://www.artstation.com/users/${handle}/quick.json`, {
diff --git a/lib/routes/asiantolick/index.ts b/lib/routes/asiantolick/index.ts
index 8034e8d95099..d63d39a9fec0 100644
--- a/lib/routes/asiantolick/index.ts
+++ b/lib/routes/asiantolick/index.ts
@@ -64,7 +64,7 @@ async function handler(ctx) {
images: image
? [
{
- src: image.prop('data-src').split(/\?/)[0],
+ src: image.prop('data-src').split(/\?/, 1)[0],
alt: image.prop('alt'),
},
]
@@ -115,7 +115,7 @@ async function handler(ctx) {
$ = load(currentResponse);
- const title = $('title').text().split(/-/)[0].trim();
+ const title = $('title').text().split(/-/, 1)[0].trim();
const icon = $('link[rel="icon"]').first().prop('href');
return {
diff --git a/lib/routes/asus/bios.tsx b/lib/routes/asus/bios.tsx
index f719b08717dc..25d01b8bc864 100644
--- a/lib/routes/asus/bios.tsx
+++ b/lib/routes/asus/bios.tsx
@@ -135,7 +135,7 @@ async function handler(ctx) {
Size: {item.FileSize}
- Download: {item.DownloadUrl.Global.split('/').pop().split('?')[0]}
+ Download: {item.DownloadUrl.Global.split('/').pop().split('?', 1)[0]}
>
)
diff --git a/lib/routes/auto-stats/index.ts b/lib/routes/auto-stats/index.ts
index 6c51ebd95256..9cf2a0ffc803 100644
--- a/lib/routes/auto-stats/index.ts
+++ b/lib/routes/auto-stats/index.ts
@@ -51,7 +51,7 @@ async function handler(ctx) {
const pubDate = title.match(/(\d{4}(?:\/\d{1,2}){2}\s\d{1,2}(?::\d{2}){2})/)?.[1] ?? undefined;
return {
- title: title.replace(/●/, '').split(/(\d+/)[0],
+ title: title.replace(/●/, '').split(/(\d+/, 1)[0],
link: new URL(item.parent().prop('href'), rootUrl).href,
pubDate: timezone(parseDate(pubDate, 'YYYY/M/D H:mm:ss'), +8),
};
diff --git a/lib/routes/bandcamp/live.ts b/lib/routes/bandcamp/live.ts
index a6c3ef81020e..03024db9f3a5 100644
--- a/lib/routes/bandcamp/live.ts
+++ b/lib/routes/bandcamp/live.ts
@@ -49,7 +49,7 @@ async function handler() {
link: item.find('.title-link').attr('href'),
title: item.find('.show-title').text(),
author: item.find('.show-artist').text(),
- pubDate: parseDate(item.find('.show-time-container').text().trim().split(' UTC')[0]),
+ pubDate: parseDate(item.find('.show-time-container').text().trim().split(' UTC', 1)[0]),
description: `
({
- title: p.title ?? p.content.split('\n')[0],
+ title: p.title ?? p.content.split('\n', 1)[0],
description: p.content.replaceAll('\n', '
') + (p.media.length && renderMedia(p.media)),
link: `${baseUrl}/post/${p.id}`,
author: p.user.name,
diff --git a/lib/routes/bdys/index.tsx b/lib/routes/bdys/index.tsx
index 45c164ea937f..b29f2ce2f3bf 100644
--- a/lib/routes/bdys/index.tsx
+++ b/lib/routes/bdys/index.tsx
@@ -150,7 +150,7 @@ async function handler(ctx) {
});
const downloadResponse = await got({
method: 'get',
- url: `${rootUrl}/downloadInfo/list?mid=${item.link.split('/')[4].split('.')[0]}`,
+ url: `${rootUrl}/downloadInfo/list?mid=${item.link.split('/', 5)[4].split('.', 1)[0]}`,
headers,
});
const content = load(detailResponse.data);
diff --git a/lib/routes/bilibili/cache.ts b/lib/routes/bilibili/cache.ts
index 213727ec6635..1825f842ea55 100644
--- a/lib/routes/bilibili/cache.ts
+++ b/lib/routes/bilibili/cache.ts
@@ -51,7 +51,7 @@ const getCookie = (disableConfig = false) => {
waitForRequest = new Promise((resolve) => {
page.on('requestfinished', async (request) => {
if (request.url() === 'https://api.bilibili.com/x/web-interface/nav') {
- const cookies = await page.cookies();
+ const cookies = await page.context().cookies();
let cookieString = cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ');
cookieString = cookieString.replace(/b_lsid=[0-9A-F]+_[0-9A-F]+/, `b_lsid=${utils.lsid()}`);
resolve(cookieString);
@@ -99,7 +99,7 @@ const getWbiVerifyString = () => {
});
const imgUrl = navResponse.data.wbi_img.img_url;
const subUrl = navResponse.data.wbi_img.sub_url;
- const r = imgUrl.slice(imgUrl.lastIndexOf('/') + 1).split('.')[0] + subUrl.slice(subUrl.lastIndexOf('/') + 1).split('.')[0];
+ const r = imgUrl.slice(imgUrl.lastIndexOf('/') + 1).split('.', 1)[0] + subUrl.slice(subUrl.lastIndexOf('/') + 1).split('.', 1)[0];
// const { body: spaceResponse } = await got('https://space.bilibili.com/1', {
// headers: {
// Referer: 'https://www.bilibili.com/',
@@ -117,7 +117,7 @@ const getWbiVerifyString = () => {
// 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57,
// 62, 11, 36, 20, 34, 44, 52,
// ];
- const array = JSON.parse(jsResponse.match(/\[(?:\d+,){63}\d+]/));
+ const array = JSON.parse(jsResponse.match(/\[(?:\d+,){63}\d+\]/));
const o = [];
for (const t of array) {
r.charAt(t) && o.push(r.charAt(t));
@@ -350,7 +350,7 @@ const getArticleDataFromCvid = async (cvid, uid) => {
const newFormatData = JSON.parse(
$('script:contains("window.__INITIAL_STATE__")')
.text()
- .match(/window\.__INITIAL_STATE__\s*=\s*(.*?);\(/)[1]
+ .match(/window\.__INITIAL_STATE__\s*=\s*(\S.*?)?;\(/)[1]
);
if (newFormatData?.readInfo?.opus?.content?.paragraphs) {
diff --git a/lib/routes/bilibili/danmaku.ts b/lib/routes/bilibili/danmaku.ts
index a29a8df35585..0f429e617725 100644
--- a/lib/routes/bilibili/danmaku.ts
+++ b/lib/routes/bilibili/danmaku.ts
@@ -72,9 +72,9 @@ async function handler(ctx) {
link,
description: `${videoName} 的 弹幕动态`,
item: danmakuList.map((item) => ({
- title: `[${processFloatTime(item.p.split(',')[0])}] ${item.text}`,
- pubDate: new Date(item.p.split(',')[4] * 1000).toUTCString(),
- guid: `${cid}-${item.p.split(',')[4]}-${item.p.split(',')[7]}`,
+ title: `[${processFloatTime(item.p.split(',', 1)[0])}] ${item.text}`,
+ pubDate: new Date(item.p.split(',', 5)[4] * 1000).toUTCString(),
+ guid: `${cid}-${item.p.split(',', 5)[4]}-${item.p.split(',', 8)[7]}`,
link,
})),
};
diff --git a/lib/routes/bilibili/fav.ts b/lib/routes/bilibili/fav.ts
index f1d104548022..1cf631e7be6a 100644
--- a/lib/routes/bilibili/fav.ts
+++ b/lib/routes/bilibili/fav.ts
@@ -40,13 +40,13 @@ async function handler(ctx) {
throw new Error(message ?? code);
}
- const userName = data.info.upper.name;
+ const username = data.info.upper.name;
const favName = data.info.title;
return {
- title: `${userName} 的 bilibili 收藏夹 ${favName}`,
+ title: `${username} 的 bilibili 收藏夹 ${favName}`,
link: `https://space.bilibili.com/${uid}/#/favlist?fid=${fid}`,
- description: `${userName} 的 bilibili 收藏夹 ${favName}`,
+ description: `${username} 的 bilibili 收藏夹 ${favName}`,
item:
data.medias &&
diff --git a/lib/routes/bilibili/user-channel.ts b/lib/routes/bilibili/user-channel.ts
index 4e8ba7ace767..b4e17b8b0e5c 100644
--- a/lib/routes/bilibili/user-channel.ts
+++ b/lib/routes/bilibili/user-channel.ts
@@ -51,7 +51,7 @@ async function handler(ctx) {
if (!channelInfo) {
return notFoundData;
}
- const [userName, face] = await cacheIn.getUsernameAndFaceFromUID(uid);
+ const [username, face] = await cacheIn.getUsernameAndFaceFromUID(uid);
const host = `https://api.bilibili.com/x/series/archives?mid=${uid}&series_id=${sid}&only_normal=true&sort=desc&pn=1&ps=${limit}`;
const response = await got(host, {
@@ -66,9 +66,9 @@ async function handler(ctx) {
}
return {
- title: `${userName} 的 bilibili 频道 ${channelInfo.meta.name}`,
+ title: `${username} 的 bilibili 频道 ${channelInfo.meta.name}`,
link,
- description: `${userName} 的 bilibili 频道`,
+ description: `${username} 的 bilibili 频道`,
image: face,
logo: face,
icon: face,
@@ -77,7 +77,7 @@ async function handler(ctx) {
description: utils.renderUGCDescription(embed, item.pic, '', item.aid, undefined, item.bvid),
pubDate: parseDate(item.pubdate, 'X'),
link: item.pubdate > utils.bvidTime && item.bvid ? `https://www.bilibili.com/video/${item.bvid}` : `https://www.bilibili.com/video/av${item.aid}`,
- author: userName,
+ author: username,
})),
};
}
diff --git a/lib/routes/bilibili/user-collection.ts b/lib/routes/bilibili/user-collection.ts
index fa09627dc240..29b3110189b5 100644
--- a/lib/routes/bilibili/user-collection.ts
+++ b/lib/routes/bilibili/user-collection.ts
@@ -43,7 +43,7 @@ async function handler(ctx) {
const limit = ctx.req.query('limit') ?? 25;
const link = `https://space.bilibili.com/${uid}/channel/collectiondetail?sid=${sid}`;
- const [userName, face] = await cache.getUsernameAndFaceFromUID(uid);
+ const [username, face] = await cache.getUsernameAndFaceFromUID(uid);
const host = `https://api.bilibili.com/x/polymer/web-space/seasons_archives_list?mid=${uid}&season_id=${sid}&sort_reverse=${sortReverse}&page_num=${page}&page_size=${limit}`;
const response = await got(host, {
@@ -58,9 +58,9 @@ async function handler(ctx) {
}
return {
- title: `${userName} 的 bilibili 合集 ${data.meta.name}`,
+ title: `${username} 的 bilibili 合集 ${data.meta.name}`,
link,
- description: `${userName} 的 bilibili 合集`,
+ description: `${username} 的 bilibili 合集`,
image: face,
logo: face,
icon: face,
@@ -69,7 +69,7 @@ async function handler(ctx) {
description: utils.renderUGCDescription(embed, item.pic, '', item.aid, undefined, item.bvid),
pubDate: parseDate(item.pubdate, 'X'),
link: item.pubdate > utils.bvidTime && item.bvid ? `https://www.bilibili.com/video/${item.bvid}` : `https://www.bilibili.com/video/av${item.aid}`,
- author: userName,
+ author: username,
})),
};
}
diff --git a/lib/routes/bilibili/video.ts b/lib/routes/bilibili/video.ts
index 9b2a66bdfae5..c34a821c3b29 100644
--- a/lib/routes/bilibili/video.ts
+++ b/lib/routes/bilibili/video.ts
@@ -140,7 +140,7 @@ async function applyCookie(page: Page, cookie: string) {
.filter((item) => item !== undefined);
if (cookies.length > 0) {
- await page.setCookie(...cookies);
+ await page.context().addCookies(cookies);
}
}
@@ -184,9 +184,9 @@ async function fetchVideoListFromBrowser(uid: string): Promise {
await applyCookie(page, cookie);
}
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- allowedBrowserRequestTypes.has(request.resourceType()) ? request.continue() : request.abort();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ allowedBrowserRequestTypes.has(request.resourceType()) ? route.continue() : route.abort();
});
},
gotoConfig: { waitUntil: 'domcontentloaded' },
diff --git a/lib/routes/bilibili/wasm-exec.ts b/lib/routes/bilibili/wasm-exec.ts
index b4359a3da0fc..2f6de00b80e6 100644
--- a/lib/routes/bilibili/wasm-exec.ts
+++ b/lib/routes/bilibili/wasm-exec.ts
@@ -1,8 +1,8 @@
-// oxlint-disable unicorn/prefer-math-trunc
+// oxlint-disable unicorn/prefer-math-trunc unicorn-js/no-this-outside-of-class unicorn-js/no-array-from-fill
// oxlint-disable no-unused-vars
/* eslint-disable prefer-rest-params */
/* eslint-disable default-case */
-/* eslint-disable unicorn/consistent-function-scoping */
+// oxlint-disable unicorn/consistent-function-scoping
/* eslint-disable no-console */
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
@@ -634,7 +634,7 @@
_makeFuncWrapper(id) {
// somehow avoiding aliasing this with an arrow function doesn't work
- // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
+ // oxlint-disable-next-line unicorn/no-this-assignment typescript/no-this-alias
const go = this;
return function () {
const event = { id, this: this, args: arguments };
diff --git a/lib/routes/bioone/featured.ts b/lib/routes/bioone/featured.ts
index a4bc770e64b9..8afafa0b09d2 100644
--- a/lib/routes/bioone/featured.ts
+++ b/lib/routes/bioone/featured.ts
@@ -40,7 +40,7 @@ async function handler(ctx) {
.toArray()
.map((item) => {
item = $(item);
- const link = item.attr('href').split('?')[0];
+ const link = item.attr('href').split('?', 1)[0];
return {
title: item.text(),
diff --git a/lib/routes/bitget/announcement.ts b/lib/routes/bitget/announcement.ts
index 3ac9b20874d3..e9d070c61d13 100644
--- a/lib/routes/bitget/announcement.ts
+++ b/lib/routes/bitget/announcement.ts
@@ -111,7 +111,7 @@ const handler: Route['handler'] = async (ctx) => {
const nextData = JSON.parse($('script#__NEXT_DATA__').text());
dataItem.description = nextData.props.pageProps.details?.content || nextData.props.pageProps.pageInitInfo?.ruleContent || item.content || '';
} catch (error: any) {
- if (error.name && (error.name === 'HTTPError' || error.name === 'RequestError' || error.name === 'FetchError')) {
+ if (error.name && ['HTTPError', 'RequestError', 'FetchError'].includes(error.name)) {
dataItem.description = item.content ?? '';
} else {
throw error;
diff --git a/lib/routes/bjsk/index.ts b/lib/routes/bjsk/index.ts
index 11bf8d02db69..e5deceeea0ae 100644
--- a/lib/routes/bjsk/index.ts
+++ b/lib/routes/bjsk/index.ts
@@ -55,7 +55,8 @@ async function handler(ctx) {
item.description = $('.article-main').html();
item.author = $('.info')
.text()
- .match(/作者:(.*)\s+来源/)[1];
+ .match(/作者:(.*?)来源/)[1]
+ .trim();
return item;
})
)
diff --git a/lib/routes/bjtu/gs.ts b/lib/routes/bjtu/gs.ts
index dec0963cf217..4711d23ba439 100644
--- a/lib/routes/bjtu/gs.ts
+++ b/lib/routes/bjtu/gs.ts
@@ -130,7 +130,7 @@ const getItem = (item, selector) => {
const newsDate = item
.find('span')
.text()
- .match(/\d{4}(-|\/|.)\d{1,2}\1\d{1,2}/)[0];
+ .match(/\d{4}(.)\d{1,2}\1\d{1,2}/)[0];
const infoTitle = newsInfo.text();
const link = rootURL + newsInfo.attr('href');
diff --git a/lib/routes/bjwxdxh/index.ts b/lib/routes/bjwxdxh/index.ts
index b90d7a8aff02..d8060cc9afcd 100644
--- a/lib/routes/bjwxdxh/index.ts
+++ b/lib/routes/bjwxdxh/index.ts
@@ -59,7 +59,7 @@ async function handler(ctx) {
const content = load(response.data);
const info = content('div.info')
.text()
- .match(/作者:(.*?)\s+发布于:(.*?\s+.*?)\s/);
+ .match(/作者:(\S*)\s+发布于:(\S*\s+.*?)\s/);
item.author = info[1];
item.pubDate = timezone(parseDate(info[2], 'YYYY-MM-DD HH:mm:ss'), +8);
item.description = content('div#con').html().trim().replaceAll('\n', '');
diff --git a/lib/routes/blockworks/index.ts b/lib/routes/blockworks/index.ts
index e1a5ad6b77c4..d204bf6e3fb9 100644
--- a/lib/routes/blockworks/index.ts
+++ b/lib/routes/blockworks/index.ts
@@ -46,7 +46,7 @@ async function handler(ctx): Promise {
limitedItems
.map((item) => ({
...item,
- link: item.link?.split('?')[0],
+ link: item.link?.split('?', 1)[0],
}))
.map((item) =>
cache.tryGet(item.link!, async () => {
diff --git a/lib/routes/blogread/index.ts b/lib/routes/blogread/index.ts
index c6091c6c17ff..3827985a28dd 100644
--- a/lib/routes/blogread/index.ts
+++ b/lib/routes/blogread/index.ts
@@ -34,7 +34,7 @@ async function handler() {
description: elem.find('dd').eq(0).text(),
link: $link.attr('href'),
author: elem.find('.small a').eq(0).text(),
- pubDate: elem.find('dd').eq(1).text().split('\n')[2],
+ pubDate: elem.find('dd').eq(1).text().split('\n', 3)[2],
};
});
return {
diff --git a/lib/routes/bloomberg/utils.ts b/lib/routes/bloomberg/utils.ts
index 215c3b716a57..c2ea1a1955fe 100644
--- a/lib/routes/bloomberg/utils.ts
+++ b/lib/routes/bloomberg/utils.ts
@@ -55,7 +55,7 @@ const apiEndpoints = {
},
};
-const pageTypeRegex1 = /\/(?[\w-]*?)\/(?\d{4}-\d{2}-\d{2}\/.*)/;
+const pageTypeRegex1 = /\/(?[\w-]*)\/(?\d{4}-\d{2}-\d{2}\/.*)/;
const pageTypeRegex2 = /(?features\/|graphics\/)(?.*)/;
const regex = [pageTypeRegex1, pageTypeRegex2];
@@ -110,7 +110,7 @@ const parseArticle = (item) =>
res = await redirectGot(apiUrl);
} catch (error) {
// fallback
- if (error.name && (error.name === 'HTTPError' || error.name === 'RequestError' || error.name === 'FetchError')) {
+ if (error.name && ['HTTPError', 'RequestError', 'FetchError'].includes(error.name)) {
try {
res = await redirectGot(item.link);
} catch {
@@ -224,7 +224,7 @@ const parseReactRendererPage = async (res, api, item) => {
return await parseStoryJson(res._data, item);
} catch (error) {
// fallback
- if (error.name && (error.name === 'HTTPError' || error.name === 'RequestError' || error.name === 'FetchError')) {
+ if (error.name && ['HTTPError', 'RequestError', 'FetchError'].includes(error.name)) {
return {
title: item.title,
link: item.link,
diff --git a/lib/routes/bluestacks/release.ts b/lib/routes/bluestacks/release.ts
index bf3c823ecf1e..19fc5b164813 100644
--- a/lib/routes/bluestacks/release.ts
+++ b/lib/routes/bluestacks/release.ts
@@ -32,11 +32,11 @@ export const route: Route = {
};
async function handler() {
- const browser = await playwright();
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' ? request.continue() : request.abort();
+ const context = await playwright();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' ? route.continue() : route.abort();
});
await page.goto(pageUrl, {
waitUntil: 'domcontentloaded',
@@ -59,10 +59,10 @@ async function handler() {
await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' ? request.continue() : request.abort();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' ? route.continue() : route.abort();
});
await page.goto(item.link, {
waitUntil: 'domcontentloaded',
@@ -79,7 +79,7 @@ async function handler() {
)
);
- await browser.close();
+ await context.close();
return {
title: $('.article__title').text().trim(),
diff --git a/lib/routes/booru/mmda.ts b/lib/routes/booru/mmda.ts
index 69cc18361fa6..779af1236384 100644
--- a/lib/routes/booru/mmda.ts
+++ b/lib/routes/booru/mmda.ts
@@ -95,7 +95,7 @@ async function handler(ctx) {
statisticsTages.find('li, br, strong').remove();
const statisticsStr = statisticsTages.text();
- const regex = /(?[^\s:]+)\s*:\s*(?.+)/gm;
+ const regex = /(?[^\s:]+)\s*:\s*(?.+)/g;
const result = {};
for (const match of statisticsStr.matchAll(regex)) {
const { key, value } = match.groups ?? ({} as { key: string; value: string });
diff --git a/lib/routes/bse/index.ts b/lib/routes/bse/index.ts
index 6154cf51cdc6..0add93200213 100644
--- a/lib/routes/bse/index.ts
+++ b/lib/routes/bse/index.ts
@@ -183,7 +183,7 @@ async function handler(ctx) {
},
});
- const data = JSON.parse(response.data.match(/null\(\[({.*})]\)/)[1]);
+ const data = JSON.parse(response.data.match(/null\(\[(\{.*\})\]\)/)[1]);
let items: DataItem[];
diff --git a/lib/routes/bsky/feeds.ts b/lib/routes/bsky/feeds.ts
index f13660c5c5b4..1b60967c7ea3 100644
--- a/lib/routes/bsky/feeds.ts
+++ b/lib/routes/bsky/feeds.ts
@@ -38,7 +38,7 @@ async function handler(ctx) {
const feeds = await getFeed(uri, cache.tryGet);
const items = feeds.feed.map(({ post }) => ({
- title: post.record.text.split('\n')[0],
+ title: post.record.text.split('\n', 1)[0],
description: renderPost({
text: post.record.text.replaceAll('\n', '
'),
embed: post.embed,
@@ -46,7 +46,7 @@ async function handler(ctx) {
}),
author: post.author.displayName,
pubDate: parseDate(post.record.createdAt),
- link: `https://bsky.app/profile/${post.author.handle}/post/${post.uri.split('app.bsky.feed.post/')[1]}`,
+ link: `https://bsky.app/profile/${post.author.handle}/post/${post.uri.split('app.bsky.feed.post/', 2)[1]}`,
upvotes: post.likeCount,
comments: post.replyCount,
}));
diff --git a/lib/routes/bsky/posts.ts b/lib/routes/bsky/posts.ts
index 27e6a602d59a..98f6efcb12d4 100644
--- a/lib/routes/bsky/posts.ts
+++ b/lib/routes/bsky/posts.ts
@@ -57,7 +57,7 @@ async function handler(ctx) {
const authorFeed = await getAuthorFeed(DID, filter, cache.tryGet);
const items = authorFeed.feed.map(({ post }) => ({
- title: post.record.text.split('\n')[0],
+ title: post.record.text.split('\n', 1)[0],
description: renderPost({
text: post.record.text.replaceAll('\n', '
'),
embed: post.embed,
@@ -65,7 +65,7 @@ async function handler(ctx) {
}),
author: post.author.displayName,
pubDate: parseDate(post.record.createdAt),
- link: `https://bsky.app/profile/${post.author.handle}/post/${post.uri.split('app.bsky.feed.post/')[1]}`,
+ link: `https://bsky.app/profile/${post.author.handle}/post/${post.uri.split('app.bsky.feed.post/', 2)[1]}`,
upvotes: post.likeCount,
comments: post.replyCount,
}));
diff --git a/lib/routes/caixin/blog.ts b/lib/routes/caixin/blog.ts
index 0a35b557dbd3..65ed02b72ae5 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];
diff --git a/lib/routes/caixin/category.ts b/lib/routes/caixin/category.ts
index 0fd9f0e2072b..9e4e7dc832c0 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 {
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/cankaoxiaoxi/index.tsx b/lib/routes/cankaoxiaoxi/index.tsx
index 2f29816ffd3e..9eb373540064 100644
--- a/lib/routes/cankaoxiaoxi/index.tsx
+++ b/lib/routes/cankaoxiaoxi/index.tsx
@@ -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/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/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/castbox/channel.ts b/lib/routes/castbox/channel.ts
index cfda05902718..0451640bcf41 100644
--- a/lib/routes/castbox/channel.ts
+++ b/lib/routes/castbox/channel.ts
@@ -50,7 +50,7 @@ You can use the RSSHub global \`limit\` query parameter to specify the maximum n
maintainers: ['ananyatimalsina'],
handler: async (ctx) => {
const { channel } = ctx.req.param();
- const cid = channel.split('-id')[1];
+ const cid = channel.split('-id', 2)[1];
if (!cid) {
throw new Error('Invalid channel format. Missing -id');
diff --git a/lib/routes/cbaigui/index.ts b/lib/routes/cbaigui/index.ts
index 9a3872640c96..47a8a98356eb 100644
--- a/lib/routes/cbaigui/index.ts
+++ b/lib/routes/cbaigui/index.ts
@@ -63,7 +63,7 @@ async function handler(ctx) {
content('p img').each((_, el) => {
const image = content(el);
- const src = image.prop('src').split('!')[0];
+ const src = image.prop('src').split('!', 1)[0];
const width = image.prop('width');
const height = image.prop('height');
diff --git a/lib/routes/cbpanet/index.ts b/lib/routes/cbpanet/index.ts
index 7aa617d5f230..6231383a0fc3 100644
--- a/lib/routes/cbpanet/index.ts
+++ b/lib/routes/cbpanet/index.ts
@@ -73,7 +73,7 @@ export const handler = async (ctx) => {
item: items,
allowEmpty: true,
image,
- author: title.split(/-/)[0],
+ author: title.split(/-/, 1)[0],
language,
};
};
diff --git a/lib/routes/ccac/news.ts b/lib/routes/ccac/news.ts
index cf893ee8e771..508c196ce03a 100644
--- a/lib/routes/ccac/news.ts
+++ b/lib/routes/ccac/news.ts
@@ -32,21 +32,21 @@ export const route: Route = {
};
async function handler(ctx) {
- const browser = await playwright();
+ const context = await playwright();
const lang = ctx.req.param('lang') ?? 'sc';
const type = utils.TYPE[ctx.req.param('type')];
const BASE = utils.langBase(lang);
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' ? request.continue() : request.abort();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' ? route.continue() : route.abort();
});
await page.goto(BASE, {
waitUntil: 'domcontentloaded',
});
const articles = await page.evaluate(() => window.articles);
- await browser.close();
+ await context.close();
const list = utils
.typeFilter(articles, type)
diff --git a/lib/routes/cccmc/index.ts b/lib/routes/cccmc/index.ts
index 2f5354f8afc3..b59b64dbbeb8 100644
--- a/lib/routes/cccmc/index.ts
+++ b/lib/routes/cccmc/index.ts
@@ -95,7 +95,7 @@ export const handler = async (ctx: Context): Promise => {
return {
title,
- description: title.split(/-/)[0].trim(),
+ description: title.split(/-/, 1)[0].trim(),
link: targetUrl,
item: items,
allowEmpty: true,
diff --git a/lib/routes/ccmn/price-adjustment.tsx b/lib/routes/ccmn/price-adjustment.tsx
index 830434cee1c8..1ce9226a6714 100644
--- a/lib/routes/ccmn/price-adjustment.tsx
+++ b/lib/routes/ccmn/price-adjustment.tsx
@@ -86,12 +86,12 @@ const READABLE_CATEGORIES = {
async function handler(ctx) {
const category = ctx.req.param('category');
- const subDomain = SUB_DOMAIN_MAP[category];
- if (!subDomain) {
+ const subdomain = SUB_DOMAIN_MAP[category];
+ if (!subdomain) {
throw new Error('未知的金属类型');
}
- const url = `https://${subDomain}`;
+ const url = `https://${subdomain}`;
const response = await got({
method: 'get',
diff --git a/lib/routes/cctv/utils/news.ts b/lib/routes/cctv/utils/news.ts
index 14ef0ce7f32d..8c1c87acf3ed 100644
--- a/lib/routes/cctv/utils/news.ts
+++ b/lib/routes/cctv/utils/news.ts
@@ -45,7 +45,7 @@ const getNews = async (category) => {
type = 'PHO';
} else if (id.startsWith('VIDE')) {
// 视频
- const vid = path.parse(image).name.split('-')[0];
+ const vid = path.parse(image).name.split('-', 1)[0];
api = `https://vdn.apps.cntv.cn/api/getHttpVideoInfo.do?pid=${vid}`;
type = 'VIDE';
} else {
diff --git a/lib/routes/cctv/utils/xinwen1j1.ts b/lib/routes/cctv/utils/xinwen1j1.ts
index a2072272c17d..7122269a864a 100644
--- a/lib/routes/cctv/utils/xinwen1j1.ts
+++ b/lib/routes/cctv/utils/xinwen1j1.ts
@@ -14,7 +14,7 @@ async function loadContent(link) {
// console.log('********')
const js_txt = '' + $('script');
- const guid = js_txt.split('guid_Ad_VideoCode = "')[1].split('";')[0];
+ const guid = js_txt.split('guid_Ad_VideoCode = "', 2)[1].split('";', 1)[0];
// console.log(guid+' js_txt********')
const { data: videoDetail } = await got({
method: 'get',
diff --git a/lib/routes/cctv/xwlb.ts b/lib/routes/cctv/xwlb.ts
index c22da35122fe..3c2bf56b506e 100644
--- a/lib/routes/cctv/xwlb.ts
+++ b/lib/routes/cctv/xwlb.ts
@@ -76,15 +76,15 @@ const getXWLB = async () => {
description: await cache.tryGet(url, async () => {
const res = await got(url);
const content = load(res.data);
- const list: string[] = [];
- content('body li').map((i, elem) => {
- const e = content(elem);
- const href = e.find('a').attr('href');
- const title = e.find('a').attr('title');
- const dur = e.find('span').text();
- list.push(`${title} ⏱${dur}`);
- return i;
- });
+ const list = content('body li')
+ .toArray()
+ .map((elem) => {
+ const e = content(elem);
+ const href = e.find('a').attr('href');
+ const title = e.find('a').attr('title');
+ const dur = e.find('span').text();
+ return `${title} ⏱${dur}`;
+ });
return list.join('
\n');
}),
};
diff --git a/lib/routes/cfr/utils.ts b/lib/routes/cfr/utils.ts
index b9dadd7ccd44..9d7fe078a459 100644
--- a/lib/routes/cfr/utils.ts
+++ b/lib/routes/cfr/utils.ts
@@ -14,7 +14,7 @@ export function getDataItem(href: string) {
const link = `${origin}${href}`;
return cache.tryGet(link, async () => {
- const prefix = href?.split('/')[1];
+ const prefix = href?.split('/', 2)[1];
const res = await ofetch(link);
const $ = load(res);
diff --git a/lib/routes/cgtn/podcast.ts b/lib/routes/cgtn/podcast.ts
index 4354e79f2380..4a5c773f58cc 100644
--- a/lib/routes/cgtn/podcast.ts
+++ b/lib/routes/cgtn/podcast.ts
@@ -45,7 +45,7 @@ async function handler(ctx) {
const data = await getData(categoryMap[category], id);
const items = data.data.map((item) => ({
title: item.title,
- pubDate: combDate(item.showDate, item.time.split(' ')[0]),
+ pubDate: combDate(item.showDate, item.time.split(' ', 1)[0]),
description: item.programSeries.content || item.detail,
itunes_item_image: item.programUrl,
itunes_duration: item.duration,
diff --git a/lib/routes/changba/user.tsx b/lib/routes/changba/user.tsx
index 26e6210ebec0..e45b8cbfd15e 100644
--- a/lib/routes/changba/user.tsx
+++ b/lib/routes/changba/user.tsx
@@ -69,14 +69,14 @@ async function handler(ctx) {
return null;
}
- workid = workid.split("'")[1];
+ workid = workid.split("'", 2)[1];
if (!workid) {
return null;
}
const mp3 = `https://upscuw.changba.com/${workid}.mp3`;
const description = renderToString();
- const itunes_item_image = $('div.work-cover').attr('style').replace(')', '').split('url(')[1];
+ const itunes_item_image = $('div.work-cover').attr('style').replace(')', '').split('url(', 2)[1];
return {
title: $('.work-title').text(),
description,
diff --git a/lib/routes/chiculture/topic.ts b/lib/routes/chiculture/topic.ts
index 276df7a1ac48..8edf833f4076 100644
--- a/lib/routes/chiculture/topic.ts
+++ b/lib/routes/chiculture/topic.ts
@@ -59,13 +59,13 @@ async function handler(ctx) {
} else if (item.title.includes('一周時事通識')) {
for (const tag of item.pubDate) {
if (/^\d{4}年$/.test(tag.title)) {
- const monthDayStr = item.title.split('- ')[1] ?? item.title.split('-')[1];
+ const monthDayStr = item.title.split('- ', 2)[1] ?? item.title.split('-', 2)[1];
item.pubDate = timezone(parseDate(monthDayStr, 'D/M'), +8);
break;
}
}
} else if (/^\d{4}年新聞回顧$/.test(item.title)) {
- item.pubDate = parseDate(`${item.title.split('年')[0]}-12-31`);
+ item.pubDate = parseDate(`${item.title.split('年', 1)[0]}-12-31`);
} else {
item.pubDate = '';
}
diff --git a/lib/routes/chikubi/nipple-video-category.ts b/lib/routes/chikubi/nipple-video-category.ts
index 5077f504b093..05e1996ddb2b 100644
--- a/lib/routes/chikubi/nipple-video-category.ts
+++ b/lib/routes/chikubi/nipple-video-category.ts
@@ -44,7 +44,7 @@ async function handler(ctx): Promise {
const items = await processItems(list);
return {
- title: `動画カテゴリー: ${feed.title?.split('-')[0]} - chikubi.jp`,
+ title: `動画カテゴリー: ${feed.title?.split('-', 1)[0]} - chikubi.jp`,
link: `${baseUrl}${url}`,
item: items,
};
diff --git a/lib/routes/chikubi/nipple-video-maker.ts b/lib/routes/chikubi/nipple-video-maker.ts
index 2d2d1a2ee686..a36def0c86ef 100644
--- a/lib/routes/chikubi/nipple-video-maker.ts
+++ b/lib/routes/chikubi/nipple-video-maker.ts
@@ -44,7 +44,7 @@ async function handler(ctx): Promise {
const items = await processItems(list);
return {
- title: `AVメーカー: ${feed.title?.split('-')[0]} - chikubi.jp`,
+ title: `AVメーカー: ${feed.title?.split('-', 1)[0]} - chikubi.jp`,
link: `${baseUrl}${url}`,
item: items,
};
diff --git a/lib/routes/chinadegrees/province.tsx b/lib/routes/chinadegrees/province.tsx
index e32c38d7da5a..e129324fe2f2 100644
--- a/lib/routes/chinadegrees/province.tsx
+++ b/lib/routes/chinadegrees/province.tsx
@@ -82,11 +82,11 @@ async function handler(ctx) {
const data = await cache.tryGet(
url,
async () => {
- const browser = await playwright();
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' ? request.continue() : request.abort();
+ const context = await playwright();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' ? route.continue() : route.abort();
});
await page.goto(url, {
waitUntil: 'domcontentloaded',
@@ -94,7 +94,7 @@ async function handler(ctx) {
await page.waitForSelector('.datalist');
const html = await page.evaluate(() => document.documentElement.innerHTML);
- await browser.close();
+ await context.close();
const $ = load(html);
return {
diff --git a/lib/routes/chinaisa/index.ts b/lib/routes/chinaisa/index.ts
index 1323ceaaaba3..464b2e0931c9 100644
--- a/lib/routes/chinaisa/index.ts
+++ b/lib/routes/chinaisa/index.ts
@@ -221,7 +221,7 @@ async function handler(ctx) {
item.title = content('div.article_title').contents().first().text() || item.title;
item.description = content('div.article_main').html();
- item.author = matches[1].split(/&/)[0];
+ item.author = matches[1].split(/&/, 1)[0];
item.guid = `chinaisa-${item.guid}`;
item.pubDate = parseDate(matches[2]);
diff --git a/lib/routes/chinania/index.ts b/lib/routes/chinania/index.ts
index 89a735d74f8f..ca1aebe5ddf1 100644
--- a/lib/routes/chinania/index.ts
+++ b/lib/routes/chinania/index.ts
@@ -62,7 +62,7 @@ export const handler = async (ctx) => {
return {
title,
- description: title.split(/-/)[0]?.trim(),
+ description: title.split(/-/, 1)[0]?.trim(),
link: currentUrl,
item: items,
allowEmpty: true,
diff --git a/lib/routes/chinaratings/credit-research.ts b/lib/routes/chinaratings/credit-research.ts
index 7baab932be09..55259a9f6a76 100644
--- a/lib/routes/chinaratings/credit-research.ts
+++ b/lib/routes/chinaratings/credit-research.ts
@@ -59,7 +59,7 @@ export const handler = async (ctx: Context): Promise => {
const metaStr: string = $$('div.newshead p span, div.title p span').text();
const pubDateStr: string | undefined = metaStr?.match(/(\d{4}-\d{2}-\d{2})/)?.[1];
- const authors: DataItem['author'] = metaStr?.match(/来源:(.*?)/)?.[1];
+ const authors: DataItem['author'] = metaStr?.match(/来源:(.*)/)?.[1];
const upDatedStr: string | undefined = pubDateStr;
let processedItem: DataItem = {
diff --git a/lib/routes/chinathinktanks/viewpoint.ts b/lib/routes/chinathinktanks/viewpoint.ts
index 021c8654ef90..98115a0fe34c 100644
--- a/lib/routes/chinathinktanks/viewpoint.ts
+++ b/lib/routes/chinathinktanks/viewpoint.ts
@@ -113,7 +113,7 @@ async function handler(ctx) {
);
return {
- title: `中国智库网 —— ${$('title').text().split('_中国智库网')[0]}`,
+ title: `中国智库网 —— ${$('title').text().split('_中国智库网', 1)[0]}`,
link,
item: items,
};
diff --git a/lib/routes/chinatimes/index.ts b/lib/routes/chinatimes/index.ts
index bfeb05a87d93..20c9bdab7a1f 100644
--- a/lib/routes/chinatimes/index.ts
+++ b/lib/routes/chinatimes/index.ts
@@ -43,7 +43,7 @@ async function handler(ctx) {
const response = await ofetch(link);
const $ = load(response);
- const browser = await playwright();
+ const context = await playwright();
const list = $('.articlebox-compact')
.toArray()
@@ -66,10 +66,10 @@ async function handler(ctx) {
const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' ? request.continue() : request.abort();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' ? route.continue() : route.abort();
});
logger.http(`Requesting ${item.link}`);
await page.goto(item.link, {
@@ -98,7 +98,7 @@ async function handler(ctx) {
)
);
- await browser.close();
+ await context.close();
return {
title: $('head title').text(),
diff --git a/lib/routes/chnmus/exhibition.tsx b/lib/routes/chnmus/exhibition.tsx
new file mode 100644
index 000000000000..8fae9bb87598
--- /dev/null
+++ b/lib/routes/chnmus/exhibition.tsx
@@ -0,0 +1,210 @@
+import { load } from 'cheerio';
+import { renderToString } from 'hono/jsx/dom/server';
+
+import type { DataItem, Route } from '@/types';
+import cache from '@/utils/cache';
+import got from '@/utils/got';
+import { parseDate } from '@/utils/parse-date';
+
+import { namespace } from './namespace';
+
+// format the date to YYYY-MM-DD and handle missing year or month
+const extractDates = (durationStr: string) => {
+ let startDate: string | undefined;
+ let endDate: string | undefined;
+
+ if (!durationStr) {
+ return { startDate, endDate };
+ }
+
+ const parts = durationStr.split(/——|[-—~]/).map((p) => p.trim()); // currently ——and- is used, add — or ~ for redundency
+ const startStr = parts[0];
+ const endStr = parts[1];
+
+ let startYear: string | undefined;
+ let startMonth: string | undefined;
+
+ const startRegex = /(\d{4})年(\d{1,2})月(\d{1,2})日/;
+ const startMatch = startStr.match(startRegex);
+
+ if (startMatch) {
+ startYear = startMatch[1];
+ startMonth = startMatch[2].padStart(2, '0');
+ const startDay = startMatch[3].padStart(2, '0');
+ startDate = `${startYear}-${startMonth}-${startDay}`;
+ }
+
+ if (endStr && startDate) {
+ const endRegex = /(?:(\d{4})年)?(?:(\d{1,2})月)?(\d{1,2})日/;
+ const endMatch = endStr.match(endRegex);
+
+ if (endMatch) {
+ const matchYear = endMatch[1];
+ const matchMonth = endMatch[2]?.padStart(2, '0');
+ const matchDay = endMatch[3].padStart(2, '0');
+
+ const finalEndYear = matchYear || startYear;
+ const finalEndMonth = matchMonth || startMonth;
+ const finalEndDay = matchDay;
+ endDate = `${finalEndYear}-${finalEndMonth}-${finalEndDay}`;
+ }
+ }
+
+ return { startDate, endDate };
+};
+
+export const route: Route = {
+ path: '/information/exhibition/:type?',
+ categories: ['travel'],
+ example: '/chnmus/information/exhibition/special',
+ parameters: {
+ type: 'Exhibition type, supported values: special(特展详情). Default: All.',
+ },
+ name: 'Special Exhibitions',
+ maintainers: ['magazian'],
+ radar: [
+ {
+ source: ['www.chnmus.net/ch/information/exhibition/index.html'],
+ target: '/information/exhibition',
+ },
+ ],
+
+ handler: async (ctx) => {
+ const type = ctx.req.param('type');
+ const isSpecial = type === 'special';
+
+ const baseUrl = 'https://www.chnmus.net';
+ const apiUrl = `${baseUrl}/ch/information/exhibition/index.html`;
+ const museumName = namespace.zh?.name || namespace.name;
+
+ const response = await got({
+ method: 'get',
+ url: apiUrl,
+ });
+
+ const $ = load(response.data);
+
+ const list = $('.col-md-6.d-flex.fadeInBottom')
+ .toArray()
+ .map((item) => {
+ const $item = $(item);
+ const link = $item.attr('href');
+ const imgUrlRaw = $item.find('.lazyload').attr('data-bg');
+ const listTitle = $item.find('.common-component-box-title').text();
+
+ return {
+ title: listTitle,
+ itemLink: `https:${link}`,
+ imgUrl: `https:${imgUrlRaw}`,
+ };
+ });
+
+ const items = await Promise.all(
+ list.map((item) => {
+ // use seperate cache key for special path
+ const cacheKey = isSpecial ? `${item.itemLink}-special` : item.itemLink;
+
+ return cache.tryGet(cacheKey, async (): Promise> => {
+ const detailResponse = await got({
+ method: 'get',
+ url: item.itemLink,
+ });
+ const content = load(detailResponse.data);
+
+ const pubDateRaw = content('.common-component-content-attribute-item')
+ .toArray()
+ .map((el) => content(el).text())
+ .find((text) => text.includes('发布日期'))!
+ .replaceAll('发布日期:', '')
+ .trim();
+ const pubDate = parseDate(pubDateRaw);
+
+ // Default path: return as news, no detail information for return
+ if (!isSpecial) {
+ return {
+ title: item.title,
+ link: item.itemLink,
+ pubDate,
+ description: renderToString(
+
+

+
+ ),
+ } as Record;
+ }
+
+ // Special path to return detail exhibition information
+ const texts = content('.common-component-content-text p')
+ .toArray()
+ .map((el) => content(el).text());
+
+ const title = texts.find((text) => text.includes('展览名称:'))?.replaceAll('展览名称:', '');
+
+ // filter out items without title, for example: https://www.chnmus.net/ch/information/exhibition/details.html?id=7400076083917230080#list
+ if (!title) {
+ return {} as Record;
+ }
+
+ const location = texts
+ .find((text) => text.includes('展览地点:'))!
+ .replaceAll('展览地点:', '')
+ .trim();
+
+ const fullDuration = texts
+ .find((text) => text.includes('展览时间:') || text.includes('开展时间:'))
+ ?.replaceAll(/(展览|开展)时间:/g, '')
+ ?.trim();
+
+ const { startDate, endDate } = extractDates(fullDuration || '');
+ const { imgUrl, itemLink } = item;
+
+ const description = renderToString(
+
+

+
+
+ 地点:
+ {location}
+
+
+ 开展:
+ {startDate ?? '未定/常设'}
+
+
+ 闭展:
+ {endDate ?? '未定/常设'}
+
+ {fullDuration && (
+
+ 原始展期:{fullDuration}
+
+ )}
+
+ );
+
+ return {
+ title,
+ link: itemLink,
+ pubDate,
+ description,
+ _extra: {
+ museumName,
+ title,
+ location,
+ startDate,
+ endDate,
+ itemLink,
+ },
+ } as Record;
+ }) as Promise;
+ })
+ );
+
+ return {
+ title: `${museumName} - 展览资讯${isSpecial ? ' - 特展详情' : ''}`,
+ link: apiUrl,
+ language: 'zh-CN',
+ item: items.filter((item) => item.title) as DataItem[],
+ };
+ },
+};
diff --git a/lib/routes/chnmus/namespace.ts b/lib/routes/chnmus/namespace.ts
new file mode 100644
index 000000000000..e5fe10c6228f
--- /dev/null
+++ b/lib/routes/chnmus/namespace.ts
@@ -0,0 +1,10 @@
+import type { Namespace } from '@/types';
+
+export const namespace: Namespace = {
+ name: 'Henan Museum',
+ url: 'www.chnmus.net',
+
+ zh: {
+ name: '河南博物院',
+ },
+};
diff --git a/lib/routes/chnmuseum/zl.tsx b/lib/routes/chnmuseum/zl.tsx
index 43f86b39e31c..71c0b0d329a6 100644
--- a/lib/routes/chnmuseum/zl.tsx
+++ b/lib/routes/chnmuseum/zl.tsx
@@ -87,12 +87,12 @@ const parseExhibitionDuration = (duration: string) => {
};
// to identify the route config and titletag based on type and subtype, this function is used in both route handler and radar to ensure consistency
-const resolveRouteConfig = (type: string | undefined, subType: string | undefined, baseUrl: string) => {
+const resolveRouteConfig = (type: string | undefined, subtype: string | undefined, baseUrl: string) => {
let url = `${baseUrl}/zl/`;
let cleanType = '';
if (type) {
- cleanType = subType ? `${type}/${subType}` : type;
+ cleanType = subtype ? `${type}/${subtype}` : type;
url = `${baseUrl}/zl/${cleanType}/`;
}
return {
@@ -103,7 +103,7 @@ const resolveRouteConfig = (type: string | undefined, subType: string | undefine
};
// to concurrent or single-page retrieval according to titletag
-const fetchTargetElements = async (cleanType: string, subType: string | undefined, url: string, baseUrl: string) => {
+const fetchTargetElements = async (cleanType: string, subtype: string | undefined, url: string, baseUrl: string) => {
const items: Array<{ $item: any; contextUrl: string }> = [];
// Use a Set to track visited links and filter out duplicate HTML elements directly at the source
// (e.g., when the same exhibition appears in both the main list and a specific sub-category list).
@@ -169,12 +169,12 @@ export const route: Route = {
],
handler: async (ctx: Context): Promise => {
const type = ctx.req.param('type')?.trim();
- const subType = ctx.req.param('subType')?.trim();
+ const subtype = ctx.req.param('subType')?.trim();
const museumName = namespace.zh?.name || namespace.name;
const baseUrl = 'https://www.chnmuseum.cn';
- const { cleanType, url, titleTag } = resolveRouteConfig(type, subType, baseUrl);
- const itemsToParse = await fetchTargetElements(cleanType, subType, url, baseUrl);
+ const { cleanType, url, titleTag } = resolveRouteConfig(type, subtype, baseUrl);
+ const itemsToParse = await fetchTargetElements(cleanType, subtype, url, baseUrl);
const list = (
await Promise.all(
diff --git a/lib/routes/cib/whpj.ts b/lib/routes/cib/whpj.ts
index ec3637e60655..4f5335070f13 100644
--- a/lib/routes/cib/whpj.ts
+++ b/lib/routes/cib/whpj.ts
@@ -44,11 +44,11 @@ async function handler(ctx) {
});
const response = await got('https://personalbank.cib.com.cn/pers/main/pubinfo/ifxQuotationQuery.do', { agent: { https: agent } });
- const cookies = response.headers['set-cookie'].map((item) => item.split(';')[0]).join(';');
+ const cookies = response.headers['set-cookie'].map((item) => item.split(';', 1)[0]).join(';');
const $ = load(response.data);
let date = $('div.main-body').find('div.labe_text').text();
- date = date.split('\n\t')[1].replace('日期:', '').trim();
+ date = date.split('\n\t', 2)[1].replace('日期:', '').trim();
date = date.slice(0, 11) + date.slice(15);
const link = 'https://personalbank.cib.com.cn/pers/main/pubinfo/ifxQuotationQuery/list?_search=false&dataSet.rows=80&dataSet.page=1&dataSet.sidx=&dataSet.sord=asc';
diff --git a/lib/routes/cih-index/report.ts b/lib/routes/cih-index/report.ts
index b3f6a0263a49..a61197bdbdfe 100644
--- a/lib/routes/cih-index/report.ts
+++ b/lib/routes/cih-index/report.ts
@@ -40,7 +40,7 @@ async function handler(ctx) {
const initialState = JSON.parse(
$('script:contains("window.__INITIAL_STATE__")')
.text()
- .match(/window\.__INITIAL_STATE__\s*=\s*({.*?});/)?.[1] || '{}'
+ .match(/window\.__INITIAL_STATE__\s*=\s*(\{.*?\});/)?.[1] || '{}'
);
const { dataResult, indNavLists, secondNameFilter, tagList, param } = initialState.data;
diff --git a/lib/routes/cisia/index.ts b/lib/routes/cisia/index.ts
index c1e5832b935f..df9098622297 100644
--- a/lib/routes/cisia/index.ts
+++ b/lib/routes/cisia/index.ts
@@ -35,7 +35,7 @@ export const handler = async (ctx) => {
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
- if (!/^https?:\/\/www\.cisia\.org(\/[^\s]*)?$/.test(item.link)) {
+ if (!/^https?:\/\/www\.cisia\.org(?:\/\S*)?$/.test(item.link)) {
return item;
}
diff --git a/lib/routes/cjlu/yjsy/index.ts b/lib/routes/cjlu/yjsy/index.ts
index 68804d6e1930..8bf31f06a120 100644
--- a/lib/routes/cjlu/yjsy/index.ts
+++ b/lib/routes/cjlu/yjsy/index.ts
@@ -86,19 +86,18 @@ async function handler(ctx) {
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10;
const url = `${host}index/${cate}.htm`;
- const { page, destroy, browser } = await getPlaywrightPage(url, {
+ const { page, destroy } = await getPlaywrightPage(url, {
onBeforeLoad: async (page) => {
await page.setExtraHTTPHeaders(headers);
- await page.setUserAgent(headers['User-Agent']);
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- allowedResourceTypes.has(request.resourceType()) ? request.continue() : request.abort();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ allowedResourceTypes.has(request.resourceType()) ? route.continue() : route.abort();
});
},
- gotoConfig: { waitUntil: 'networkidle2' },
+ gotoConfig: { waitUntil: 'networkidle' },
});
- const cookies = await browser.cookies();
+ const cookies = await page.context().cookies();
const cookieString = cookies.map((c) => `${c.name}=${c.value}`).join('; ');
const response = await page.content();
diff --git a/lib/routes/cline/blog.ts b/lib/routes/cline/blog.ts
index 7455759c8df2..4ae95ce50cea 100644
--- a/lib/routes/cline/blog.ts
+++ b/lib/routes/cline/blog.ts
@@ -21,7 +21,7 @@ function extractArticlesFromDOM($: CheerioAPI): DataItem[] {
// Extract date and author with single regex
const metaText = element.find('.text-sm.text-slate-500').text().trim();
- const metaMatch = metaText.match(/^([^•]+)\s*•\s*([A-Za-z]+\s+\d{1,2},?\s+\d{4})/);
+ const metaMatch = metaText.match(/^([^•]+)•\s*([A-Z]+\s+\d{1,2},?\s+\d{4})/i);
const author = metaMatch ? metaMatch[1].trim() : 'Cline Team';
const pubDate = metaMatch ? parseDate(metaMatch[2]) : undefined;
diff --git a/lib/routes/cmde/index.ts b/lib/routes/cmde/index.ts
index 0f7776e45237..6e3d8c1cfcb7 100644
--- a/lib/routes/cmde/index.ts
+++ b/lib/routes/cmde/index.ts
@@ -18,12 +18,12 @@ export const route: Route = {
async function handler(ctx) {
const cate = ctx.req.param('cate') ?? 'xwdt/zxyw';
const url = `${rootURL}/${cate}/`;
- const browser = await playwright();
+ const context = await playwright();
const data = await cache.tryGet(url, async () => {
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' ? request.continue() : request.abort();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' ? route.continue() : route.abort();
});
await page.goto(url, {
waitUntil: 'domcontentloaded',
@@ -52,10 +52,10 @@ async function handler(ctx) {
const items = await Promise.all(
data.items.map((item) =>
cache.tryGet(item.link, async () => {
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' ? request.continue() : request.abort();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' ? route.continue() : route.abort();
});
await page.goto(item.link, {
waitUntil: 'domcontentloaded',
@@ -72,7 +72,7 @@ async function handler(ctx) {
)
);
- await browser.close();
+ await context.close();
return {
title: data.title,
diff --git a/lib/routes/cnbeta/common.ts b/lib/routes/cnbeta/common.ts
index a96f3a49b5c8..60f2e61236bc 100644
--- a/lib/routes/cnbeta/common.ts
+++ b/lib/routes/cnbeta/common.ts
@@ -25,7 +25,7 @@ export async function handler(ctx) {
? response.data.result.list.map((item) => ({
title: item.title,
description: item.hometext,
- author: item.source.split('@http')[0],
+ author: item.source.split('@http', 1)[0],
pubDate: timezone(parseDate(item.inputtime), +8),
link: item.url_show.startsWith('//') ? `https:${item.url_show}` : item.url_show.replace('http:', 'https:'),
category: item.label.name,
diff --git a/lib/routes/cncf/index.ts b/lib/routes/cncf/index.ts
index a4d07888f806..b3e48a90a757 100644
--- a/lib/routes/cncf/index.ts
+++ b/lib/routes/cncf/index.ts
@@ -40,7 +40,7 @@ async function handler(ctx) {
.map((item) => ({
title: $(item).find('span.post-archive__title').text().trim(),
link: $(item).find('span.post-archive__title > a').attr('href'),
- pubDate: parseDate($(item).find('span.post-archive__item_date').text().split('|')[0]),
+ pubDate: parseDate($(item).find('span.post-archive__item_date').text().split('|', 1)[0]),
}));
const items = await Promise.all(
diff --git a/lib/routes/cnljxh/index.ts b/lib/routes/cnljxh/index.ts
index a78a42d0df14..29555063e9c6 100644
--- a/lib/routes/cnljxh/index.ts
+++ b/lib/routes/cnljxh/index.ts
@@ -55,7 +55,7 @@ export const handler = async (ctx: Context): Promise => {
const title: string = $$('div.content_title h2').text();
const description: string | undefined = $$('div.content_div').html() ?? '';
- const authors: DataItem['author'] = $$('div.content_title p').text().split(/\s/)[0]?.split(/:/).pop();
+ const authors: DataItem['author'] = $$('div.content_title p').text().split(/\s/, 1)[0]?.split(/:/).pop();
let processedItem: DataItem = {
title,
diff --git a/lib/routes/cockroachlabs/blog.ts b/lib/routes/cockroachlabs/blog.ts
index a5ef5cc2394a..f23a108eea52 100644
--- a/lib/routes/cockroachlabs/blog.ts
+++ b/lib/routes/cockroachlabs/blog.ts
@@ -88,7 +88,7 @@ async function handler(ctx) {
try {
const date = new Date(dateText);
if (!Number.isNaN(date.getTime())) {
- pubDate = parseDate(date.toISOString().split('T')[0]);
+ pubDate = parseDate(date.toISOString().split('T', 1)[0]);
}
} catch {
// Ignore parsing errors
diff --git a/lib/routes/codefather/posts.ts b/lib/routes/codefather/posts.ts
index 84412c0dbce2..9953e2dae37a 100644
--- a/lib/routes/codefather/posts.ts
+++ b/lib/routes/codefather/posts.ts
@@ -88,7 +88,7 @@ async function handler(ctx) {
}
return {
- title: content.split('\n')[0] || '无标题',
+ title: content.split('\n', 1)[0] || '无标题',
link: `https://www.codefather.cn/post/${item.id}`,
description,
pubDate: parseDate(item.createTime as number),
diff --git a/lib/routes/coindesk/utils.ts b/lib/routes/coindesk/utils.ts
index a8ed6705a47b..c5124704cfcc 100644
--- a/lib/routes/coindesk/utils.ts
+++ b/lib/routes/coindesk/utils.ts
@@ -10,7 +10,7 @@ export const parseItem = async (item) => {
$('.article-ad, #strategy-rules-player-wrapper, [data-module-name="newsletter-article-sign-up-module"], div.flex.flex-col.gap-2').remove();
const cover = $('.article-content-wrapper figure');
- cover.find('img').attr('src', cover.find('img').attr('url')?.split('?')[0]);
+ cover.find('img').attr('src', cover.find('img').attr('url')?.split('?', 1)[0]);
cover.find('img').removeAttr('style srcset url');
item.description =
@@ -21,7 +21,7 @@ export const parseItem = async (item) => {
.join('');
item.pubDate = parseDate(ldJson.datePublished);
item.author = ldJson.author.map((a) => ({ name: a.name }));
- item.image = ldJson.image.url.split('?')[0];
+ item.image = ldJson.image.url.split('?', 1)[0];
return item;
};
diff --git a/lib/routes/cointelegraph/index.ts b/lib/routes/cointelegraph/index.ts
index e984e6d7f64c..33bb1d2d1e71 100644
--- a/lib/routes/cointelegraph/index.ts
+++ b/lib/routes/cointelegraph/index.ts
@@ -41,7 +41,7 @@ async function handler(): Promise {
.filter((item) => item.link && /\/news|\/explained|\/innovation-circle/.test(item.link))
.map((item) => ({
...item,
- link: item.link?.split('?')[0],
+ link: item.link?.split('?', 1)[0],
}))
.map((item) =>
cache.tryGet(item.link!, async () => {
diff --git a/lib/routes/colamanga/manga.ts b/lib/routes/colamanga/manga.ts
index 626f1b0d8067..bda4c200b721 100644
--- a/lib/routes/colamanga/manga.ts
+++ b/lib/routes/colamanga/manga.ts
@@ -43,14 +43,13 @@ async function handler(ctx: Context) {
const id = ctx.req.param('id');
const url = `https://${domain}/${id}`;
- const browser = await playwright();
+ const context = await playwright();
- const page = await browser.newPage();
+ const page = await context.newPage();
- await page.setRequestInterception(true);
-
- page.on('request', (request) => {
- request.resourceType() === 'document' ? request.continue() : request.abort();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' ? route.continue() : route.abort();
});
logger.http(`Requesting ${url}`);
@@ -60,7 +59,7 @@ async function handler(ctx: Context) {
});
const response = await page.content();
- await browser.close();
+ await context.close();
const $ = load(response);
diff --git a/lib/routes/comicat/search.ts b/lib/routes/comicat/search.ts
index 46d191f7aa86..6051a5916418 100644
--- a/lib/routes/comicat/search.ts
+++ b/lib/routes/comicat/search.ts
@@ -44,8 +44,8 @@ async function handler(ctx) {
const { data: response } = await got(item.link);
const $ = load(response);
- item.pubDate = parseDate($('div.main > div.slayout > div > div.c1 > div:nth-child(1) > div > p:nth-child(4)').text().split('发布时间: ')[1]);
- const marginLink = `magnet:?xt=urn:btih:${$('#text_hash_id').text().split(',特征码:')[1]}`.trim();
+ item.pubDate = parseDate($('div.main > div.slayout > div > div.c1 > div:nth-child(1) > div > p:nth-child(4)').text().split('发布时间: ', 2)[1]);
+ const marginLink = `magnet:?xt=urn:btih:${$('#text_hash_id').text().split(',特征码:', 2)[1]}`.trim();
item.enclosure_url = marginLink;
item.enclosure_type = 'application/x-bittorrent';
item.description = $('#btm > div.main > div.slayout > div > div.c2 > div:nth-child(1) > div.intro').html();
diff --git a/lib/routes/cool18/index.ts b/lib/routes/cool18/index.ts
index f9d9da1b1453..96b9f7b50aef 100644
--- a/lib/routes/cool18/index.ts
+++ b/lib/routes/cool18/index.ts
@@ -65,7 +65,7 @@ function buildUrl(rootUrl: string, type: PostType, keyword: string | undefined,
function extractHomeList($: CheerioAPI, rootUrl: string, limit: number): DataItem[] {
try {
const scriptText = $('script:contains("_PageData")').text();
- const match = scriptText.match(/const\s+_PageData\s*=\s*(\[[\s\S]*?]);/);
+ const match = scriptText.match(/const\s+_PageData\s*=\s*(\[[\s\S]*?\]);/);
if (!match?.[1]) {
return [];
diff --git a/lib/routes/coolbuy/index.tsx b/lib/routes/coolbuy/index.tsx
index 0c33ba8ad7a6..702c79cc900f 100644
--- a/lib/routes/coolbuy/index.tsx
+++ b/lib/routes/coolbuy/index.tsx
@@ -29,8 +29,8 @@ export const handler = async (ctx: Context): Promise => {
const items: DataItem[] = response.objects.slice(0, limit).map((item): DataItem => {
const title: string = item.title;
- const image: string | undefined = item.cover_image?.split(/\?/)?.[0];
- const banner: string | undefined = item.display_image?.split(/\?/)?.[0];
+ const image: string | undefined = item.cover_image?.split(/\?/, 1)?.[0];
+ const banner: string | undefined = item.display_image?.split(/\?/, 1)?.[0];
const images = [banner, image].filter(Boolean).map((image) => ({
src: image,
diff --git a/lib/routes/cryptoslate/index.ts b/lib/routes/cryptoslate/index.ts
index abebfd6e0c93..77c4725f6587 100644
--- a/lib/routes/cryptoslate/index.ts
+++ b/lib/routes/cryptoslate/index.ts
@@ -46,7 +46,7 @@ async function handler(ctx): Promise {
try {
// Clean URL by removing query parameters
- const cleanUrl = item.link.split('?')[0];
+ const cleanUrl = item.link.split('?', 1)[0];
return {
title: item.title || 'Untitled',
diff --git a/lib/routes/ctinews/topic.ts b/lib/routes/ctinews/topic.ts
index ec1d4b745feb..9560bcacbfb1 100644
--- a/lib/routes/ctinews/topic.ts
+++ b/lib/routes/ctinews/topic.ts
@@ -88,7 +88,7 @@ async function handler(ctx) {
const $ = load(response);
if (item.link?.includes('/videos/')) {
const ldJson = JSON.parse($('script[type="application/ld+json"]:contains("VideoObject")').text());
- const videoId = ldJson.embedUrl.match(/embed\/([a-zA-Z0-9_-]+)/)?.[1];
+ const videoId = ldJson.embedUrl.match(/embed\/([\w-]+)/)?.[1];
item.description =
`
` +
diff --git a/lib/routes/cw/author.ts b/lib/routes/cw/author.ts
index 2be67a126dfe..fda1501a0996 100644
--- a/lib/routes/cw/author.ts
+++ b/lib/routes/cw/author.ts
@@ -27,11 +27,11 @@ export const route: Route = {
};
async function handler(ctx) {
- const browser = await playwright();
+ const context = await playwright();
- const { $, items } = await parsePage('author', browser, ctx);
+ const { $, items } = await parsePage('author', context, ctx);
- await browser.close();
+ await context.close();
return {
title: $('head title').text(),
diff --git a/lib/routes/cw/master.ts b/lib/routes/cw/master.ts
index eaae855f3f21..239d6c22a334 100644
--- a/lib/routes/cw/master.ts
+++ b/lib/routes/cw/master.ts
@@ -37,11 +37,11 @@ export const route: Route = {
};
async function handler(ctx) {
- const browser = await playwright();
+ const context = await playwright();
- const { $, items } = await parsePage('master', browser, ctx);
+ const { $, items } = await parsePage('master', context, ctx);
- await browser.close();
+ await context.close();
return {
title: $('head title').text(),
diff --git a/lib/routes/cw/sub.ts b/lib/routes/cw/sub.ts
index 3f5abdb728bb..a374c6d1921c 100644
--- a/lib/routes/cw/sub.ts
+++ b/lib/routes/cw/sub.ts
@@ -22,11 +22,11 @@ export const route: Route = {
};
async function handler(ctx) {
- const browser = await playwright();
+ const context = await playwright();
- const { $, items } = await parsePage('sub', browser, ctx);
+ const { $, items } = await parsePage('sub', context, ctx);
- await browser.close();
+ await context.close();
return {
title: $('head title').text(),
diff --git a/lib/routes/cw/today.ts b/lib/routes/cw/today.ts
index dd34056332ab..73e4303f4c0e 100644
--- a/lib/routes/cw/today.ts
+++ b/lib/routes/cw/today.ts
@@ -28,11 +28,11 @@ export const route: Route = {
};
async function handler(ctx) {
- const browser = await playwright();
+ const context = await playwright();
- const { $, items } = await parsePage('today', browser, ctx);
+ const { $, items } = await parsePage('today', context, ctx);
- await browser.close();
+ await context.close();
return {
title: $('head title').text(),
diff --git a/lib/routes/cw/utils.ts b/lib/routes/cw/utils.ts
index f1f9a9859505..be75a2aafa30 100644
--- a/lib/routes/cw/utils.ts
+++ b/lib/routes/cw/utils.ts
@@ -1,5 +1,6 @@
import { load } from 'cheerio';
+import { config } from '@/config';
import cache from '@/utils/cache';
import logger from '@/utils/logger';
import ofetch from '@/utils/ofetch';
@@ -29,13 +30,13 @@ const pathMap = {
},
};
-const getCookie = async (browser, tryGet) => {
+const getCookie = async (context, tryGet) => {
if (!cookie) {
cookie = await tryGet('cw:cookie', async () => {
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' ? request.continue() : request.abort();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' ? route.continue() : route.abort();
});
logger.http(`Requesting ${baseUrl}/user/get/cookie-bar`);
await page.goto(`${baseUrl}/user/get/cookie-bar`, {
@@ -49,14 +50,14 @@ const getCookie = async (browser, tryGet) => {
return cookie;
};
-const parsePage = async (path, browser, ctx) => {
+const parsePage = async (path, context, ctx) => {
const pageUrl = `${baseUrl}${pathMap[path].pageUrl(ctx.req.param('channel'))}`;
- const cookie = await getCookie(browser, cache.tryGet);
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' ? request.continue() : request.abort();
+ const cookie = await getCookie(context, cache.tryGet);
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' ? route.continue() : route.abort();
});
await setCookies(page, cookie, 'cw.com.tw');
logger.http(`Requesting ${pageUrl}`);
@@ -70,7 +71,7 @@ const parsePage = async (path, browser, ctx) => {
const $ = load(response);
const list = parseList($, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : pathMap[path].limit);
- const items = await parseItems(list, browser, cache.tryGet);
+ const items = await parseItems(list, context, cache.tryGet);
return { $, items };
};
@@ -88,14 +89,14 @@ const parseList = ($, limit) =>
})
.slice(0, limit);
-const parseItems = (list, browser, tryGet) =>
+const parseItems = (list, context, tryGet) =>
Promise.all(
list.map((item) =>
tryGet(item.link, async () => {
const response = await ofetch(item.link, {
headers: {
- Cookie: await getCookie(browser, tryGet),
- 'User-Agent': browser.userAgent(),
+ Cookie: await getCookie(context, tryGet),
+ 'User-Agent': config.ua,
},
});
const $ = load(response);
diff --git a/lib/routes/cyzone/util.ts b/lib/routes/cyzone/util.ts
index 2f61635d0a83..76f8a6c5ff4f 100644
--- a/lib/routes/cyzone/util.ts
+++ b/lib/routes/cyzone/util.ts
@@ -19,7 +19,7 @@ const getInfo = (url, tryGet) =>
const $ = load(response);
- const avatar = $('img.avatar')?.prop('src')?.split('?')[0] ?? undefined;
+ const avatar = $('img.avatar')?.prop('src')?.split('?', 1)[0] ?? undefined;
const icon = new URL($('link[rel="icon"]')?.prop('href'), rootUrl).href;
const image = new URL($('div.logo img')?.prop('src'), rootUrl).href;
@@ -91,7 +91,7 @@ const processItems = async (apiUrl, limit, tryGet, ...params) => {
content('img').each((_, el) => {
if (content(el).prop('src')) {
- content(el).prop('src', content(el).prop('src').split('?')[0]);
+ content(el).prop('src', content(el).prop('src').split('?', 1)[0]);
} else {
content(el).remove();
}
diff --git a/lib/routes/dailypush/all.ts b/lib/routes/dailypush/all.ts
index 6a39fe133178..e92405045348 100644
--- a/lib/routes/dailypush/all.ts
+++ b/lib/routes/dailypush/all.ts
@@ -42,12 +42,12 @@ async function handler(ctx) {
const { sort = '' } = ctx.req.param();
const url = sort ? `${BASE_URL}/${sort}` : BASE_URL;
- const browser = await playwright();
+ const context = await playwright();
try {
- const html = await fetchPageHtml(browser, url, 'article');
+ const html = await fetchPageHtml(context, url, 'article');
const $ = load(html);
const list = parseArticles($, BASE_URL);
- const items = await enhanceItemsWithSummaries(browser, list);
+ const items = await enhanceItemsWithSummaries(context, list);
const pageTitle = $('title').text() || 'DailyPush - All';
@@ -57,6 +57,6 @@ async function handler(ctx) {
item: items,
};
} finally {
- await browser.close();
+ await context.close();
}
}
diff --git a/lib/routes/dailypush/tags.ts b/lib/routes/dailypush/tags.ts
index c1430d29ae47..0cac69fc7345 100644
--- a/lib/routes/dailypush/tags.ts
+++ b/lib/routes/dailypush/tags.ts
@@ -43,12 +43,12 @@ async function handler(ctx) {
const { tag, sort = 'trending' } = ctx.req.param();
const url = `${BASE_URL}/${tag}/${sort}`;
- const browser = await playwright();
+ const context = await playwright();
try {
- const html = await fetchPageHtml(browser, url, 'article');
+ const html = await fetchPageHtml(context, url, 'article');
const $ = load(html);
const list = parseArticles($, BASE_URL);
- const items = await enhanceItemsWithSummaries(browser, list);
+ const items = await enhanceItemsWithSummaries(context, list);
const pageTitle = $('title').text() || `DailyPush - ${tag.charAt(0).toUpperCase() + tag.slice(1)}`;
@@ -58,6 +58,6 @@ async function handler(ctx) {
item: items,
};
} finally {
- await browser.close();
+ await context.close();
}
}
diff --git a/lib/routes/dailypush/utils.ts b/lib/routes/dailypush/utils.ts
index 1fd24ec3ad85..134eca52ff32 100644
--- a/lib/routes/dailypush/utils.ts
+++ b/lib/routes/dailypush/utils.ts
@@ -1,11 +1,12 @@
import type { CheerioAPI } from 'cheerio';
import { load } from 'cheerio';
+import type { BrowserContext } from 'patchright';
import type { DataItem } from '@/types';
import cache from '@/utils/cache';
import logger from '@/utils/logger';
import { parseRelativeDate } from '@/utils/parse-date';
-import type { Browser, Page } from '@/utils/playwright';
+import type { Page } from '@/utils/playwright';
export const BASE_URL = 'https://www.dailypush.dev';
@@ -23,19 +24,19 @@ export interface ArticleItem {
const allowedRequestTypes = new Set(['document']);
async function preparePage(page: Page) {
- await page.setRequestInterception(true);
- page.on('request', (request) => {
+ await page.route('**/*', (route) => {
+ const request = route.request();
if (allowedRequestTypes.has(request.resourceType())) {
- request.continue();
+ route.continue();
return;
}
- request.abort();
+ route.abort();
});
}
-export async function fetchPageHtml(browser: Browser, url: string, waitForSelector?: string): Promise {
- const page = await browser.newPage();
+export async function fetchPageHtml(context: BrowserContext, url: string, waitForSelector?: string): Promise {
+ const page = await context.newPage();
await preparePage(page);
try {
@@ -143,7 +144,7 @@ function extractCategories(article: ReturnType, $: CheerioAPI): stri
const tagText = tagElement.text().trim();
// Skip summary/stats links and navigation
- if (tagHref && tagText && !tagHref.includes('article/') && !tagHref.includes('Summary') && tagText.length < 50 && !/^(Summary|stats|About|Tags|Toggle|Trending|Latest|Previous|Next)$/i.test(tagText)) {
+ if (tagHref && tagText && !tagHref.includes('article/') && !tagHref.includes('Summary') && tagText.length < 50 && !/^(?:Summary|stats|About|Tags|Toggle|Trending|Latest|Previous|Next)$/i.test(tagText)) {
return tagText;
}
return null;
@@ -259,9 +260,9 @@ export function parseArticles($: CheerioAPI, baseUrl: string): ArticleItem[] {
/**
* Enhance items with full summaries from dailypush article pages.
- * Uses the provided browser; opens a new tab per URL (document requests only). Caller must close the browser.
+ * Uses the provided context; opens a new tab per URL (document requests only). Caller must close the context.
*/
-export async function enhanceItemsWithSummaries(browser: Browser, items: ArticleItem[]): Promise {
+export async function enhanceItemsWithSummaries(context: BrowserContext, items: ArticleItem[]): Promise {
const itemsWithUrl = items.filter((item) => item.dailyPushUrl !== undefined);
const itemsWithoutUrl: DataItem[] = items.filter((item) => item.dailyPushUrl === undefined);
@@ -269,7 +270,7 @@ export async function enhanceItemsWithSummaries(browser: Browser, items: Article
itemsWithUrl.map((item) =>
cache.tryGet(item.dailyPushUrl!, async () => {
try {
- const html = await fetchPageHtml(browser, item.dailyPushUrl!, 'p.font-ibm-plex-sans.leading-relaxed');
+ const html = await fetchPageHtml(context, item.dailyPushUrl!, 'p.font-ibm-plex-sans.leading-relaxed');
const $ = load(html);
const summary = $('p.font-ibm-plex-sans.leading-relaxed');
if (summary.length > 0 && summary.text().trim()) {
diff --git a/lib/routes/daum/potplayer.ts b/lib/routes/daum/potplayer.ts
index 919afa2166b1..960c98389b6d 100644
--- a/lib/routes/daum/potplayer.ts
+++ b/lib/routes/daum/potplayer.ts
@@ -20,14 +20,14 @@ export const handler = async (ctx: Context): Promise => {
// Group 3: Trailing hyphens (unused, but for context)
// Group 4: Update content
// Uses global and multiline flags for all matches and line start/end anchors
- const updateRegex = /^(-+)\s*\n(.*?)\s*\n(-+)\s*\n([\s\S]*?)(?=\n-{2,}|<\/p>)/gm;
+ const updateRegex = /^-+[^\S\n]*\n(.*)\r?\n-+[^\S\n]*\n([\s\S]*?)(?=\n-{2}|<\/p>)/gm;
const items: DataItem[] = [];
let match: RegExpExecArray | null;
while ((match = updateRegex.exec(response)) !== null && items.length < limit) {
- const headerLine: string | undefined = match[2].trim();
- const description: string | undefined = match[4].trim()?.replaceAll(/(\s[+-])/g, '
$1');
+ const headerLine: string | undefined = match[1].trim();
+ const description: string | undefined = match[2].trim()?.replaceAll(/(\s[+-])/g, '
$1');
let version = 'N/A';
let pubDateStr: string | undefined = undefined;
diff --git a/lib/routes/dayanzai/index.ts b/lib/routes/dayanzai/index.ts
index abb37cb3ab5c..11db2149fedd 100644
--- a/lib/routes/dayanzai/index.ts
+++ b/lib/routes/dayanzai/index.ts
@@ -41,7 +41,7 @@ async function handler(ctx) {
const response = await got.get(currentUrl);
const $ = load(response.data);
const lists = $('div.c-box > div > div.c-zx-list > ul > li');
- const reg = /日期:(.*?(\s\(.*?\))?)\s/;
+ const reg = /日期:(.*?(?:\s\(.*?\))?)\s/;
const list = lists.toArray().map((item) => {
item = $(item).find('div');
let date = reg.exec(item.find('div.r > p.other').text())[1];
diff --git a/lib/routes/dbaplus/new.ts b/lib/routes/dbaplus/new.ts
index 7e877f78a079..4518097bb1d3 100644
--- a/lib/routes/dbaplus/new.ts
+++ b/lib/routes/dbaplus/new.ts
@@ -135,13 +135,13 @@ export const handler = async (ctx: Context): Promise => {
const description: string = $('meta[name="description"]').attr('content') ?? '';
return {
- title: $('title').text().split(/:/)[0],
+ title: $('title').text().split(/:/, 1)[0],
description,
link: targetUrl,
item: items,
allowEmpty: true,
image: $('div.navbar-header img').attr('src'),
- author: description.split(/:/)[0],
+ author: description.split(/:/, 1)[0],
language,
id: targetUrl,
};
diff --git a/lib/routes/dcard/section.ts b/lib/routes/dcard/section.ts
index a32f14582405..9c30bc07c7fa 100644
--- a/lib/routes/dcard/section.ts
+++ b/lib/routes/dcard/section.ts
@@ -26,7 +26,7 @@ export const route: Route = {
async function handler(ctx) {
const { type = 'latest', section = 'posts' } = ctx.req.param();
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30;
- const browser = await playwright();
+ const context = await playwright();
let link = 'https://www.dcard.tw/f';
let api = 'https://www.dcard.tw/service/api/v2';
@@ -48,10 +48,10 @@ async function handler(ctx) {
title += '最新';
}
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' ? request.continue() : request.abort();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' ? route.continue() : route.abort();
});
await page.setExtraHTTPHeaders({
referer: `https://www.dcard.tw/f/${section}`,
@@ -60,7 +60,7 @@ async function handler(ctx) {
await page.goto(`${api}&limit=100`);
await page.waitForSelector('body > pre');
const response = await page.evaluate(() => document.querySelector('body > pre').textContent);
- const cookies = await cache.tryGet('dcard:cookies', () => page.cookies(), 3600, false);
+ const cookies = await cache.tryGet('dcard:cookies', () => page.context().cookies(), 3600, false);
await page.close();
const data = JSON.parse(response);
@@ -76,8 +76,8 @@ async function handler(ctx) {
}));
// parse fulltext for first `limit` items
- const result = await utils.ProcessFeed(items, cookies, browser, limit, cache);
- await browser.close();
+ const result = await utils.ProcessFeed(items, cookies, context, limit, cache);
+ await context.close();
return {
title,
diff --git a/lib/routes/dcard/utils.ts b/lib/routes/dcard/utils.ts
index 083c736d6dd6..fffa67a36763 100644
--- a/lib/routes/dcard/utils.ts
+++ b/lib/routes/dcard/utils.ts
@@ -1,6 +1,6 @@
import pMap from 'p-map';
-const ProcessFeed = async (items, cookies, browser, limit, cache) => {
+const ProcessFeed = async (items, cookies, context, limit, cache) => {
let newCookies = [];
const result = await pMap(
items.slice(0, limit),
@@ -10,25 +10,25 @@ const ProcessFeed = async (items, cookies, browser, limit, cache) => {
let response;
// try catch 处理被删除的帖子
try {
- const page = await browser.newPage();
- await page.setRequestInterception(true);
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' || request.resourceType() === 'fetch' || request.resourceType() === 'xhr' ? request.continue() : request.abort();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' || request.resourceType() === 'fetch' || request.resourceType() === 'xhr' ? route.continue() : route.abort();
});
await page.setExtraHTTPHeaders({
referer: `https://www.dcard.tw/f/${i.forumAlias}/p/${i.id}`,
});
- await page.setCookie(...cookies);
+ await page.context().addCookies(cookies);
await page.goto(url);
await page.waitForSelector('body > pre');
response = await page.evaluate(() => document.querySelector('body > pre').textContent);
- newCookies = await page.cookies();
+ newCookies = await page.context().cookies();
await page.close();
const data = JSON.parse(response);
let body = data.content;
body = body.replaceAll(/(?=https?:\/\/).*?(?<=\.(jpe?g|gif|png))/gi, (m) => `
`);
- body = body.replaceAll(/(?=https?:\/\/).*(??)$/gim, (m) => `${m}`);
+ body = body.replaceAll(/(?=https?:\/\/).+(??)$/gim, (m) => `${m}`);
body = body.replaceAll('\n', '
');
return body;
diff --git a/lib/routes/dcfever/utils.tsx b/lib/routes/dcfever/utils.tsx
index 487067ee34a7..7045d8fa49d5 100644
--- a/lib/routes/dcfever/utils.tsx
+++ b/lib/routes/dcfever/utils.tsx
@@ -76,7 +76,7 @@ const parseItem = (item) =>
content.find('img').each((_, e) => {
if (e.attribs.src?.includes('?')) {
- e.attribs.src = e.attribs.src.split('?')[0];
+ e.attribs.src = e.attribs.src.split('?', 1)[0];
}
});
diff --git a/lib/routes/deadline/posts.tsx b/lib/routes/deadline/posts.tsx
index dd9a5276988a..0d285abb2fc2 100644
--- a/lib/routes/deadline/posts.tsx
+++ b/lib/routes/deadline/posts.tsx
@@ -54,7 +54,7 @@ async function handler(ctx) {
$('.c-lazy-image__img').each((_, img) => {
img = $(img);
if (img.attr('data-lazy-src')) {
- img.attr('src', img.attr('data-lazy-src').split('?')[0]);
+ img.attr('src', img.attr('data-lazy-src').split('?', 1)[0]);
img.removeAttr('data-lazy-src');
img.removeAttr('data-lazy-srcset');
}
diff --git a/lib/routes/dealstreetasia/home.ts b/lib/routes/dealstreetasia/home.ts
index e0959e5b0ef0..177bbf62e7ce 100644
--- a/lib/routes/dealstreetasia/home.ts
+++ b/lib/routes/dealstreetasia/home.ts
@@ -60,7 +60,7 @@ async function fetchPage() {
link: item.post_url || item.link || '',
description: item.post_excerpt || item.excerpt || '',
pubDate: item.post_date ? new Date(item.post_date).toUTCString() : item.date ? new Date(item.date).toUTCString() : '',
- category: item.category_link ? item.category_link.replaceAll(/(<([^>]+)>)/gi, '') : '', // Clean HTML if category_link exists
+ category: item.category_link ? item.category_link.replaceAll(/(<([^>]+)>)/g, '') : '', // Clean HTML if category_link exists
image: item.image_url ? item.image_url.replace(/\?.*$/, '') : '', // Remove query parameters if image_url exists
}));
diff --git a/lib/routes/decrypt/index.ts b/lib/routes/decrypt/index.ts
index 6d9896371dcc..78fef7c0ea2b 100644
--- a/lib/routes/decrypt/index.ts
+++ b/lib/routes/decrypt/index.ts
@@ -52,7 +52,7 @@ async function handler(ctx): Promise {
const result = await extractFullText(item.link);
return {
title: item.title || 'Untitled',
- link: item.link.split('?')[0], // Clean URL by removing query parameters
+ link: item.link.split('?', 1)[0], // Clean URL by removing query parameters
pubDate: item.pubDate ? parseDate(item.pubDate) : undefined,
description: result?.fullText ?? (item.content || ''),
author: item.creator || 'Decrypt',
@@ -66,7 +66,7 @@ async function handler(ctx): Promise {
// Fallback to RSS content
return {
title: item.title || 'Untitled',
- link: item.link.split('?')[0],
+ link: item.link.split('?', 1)[0],
pubDate: item.pubDate ? parseDate(item.pubDate) : undefined,
description: item.content || '',
author: item.creator || 'Decrypt',
diff --git a/lib/routes/dedao/index.ts b/lib/routes/dedao/index.ts
index 773cf893b1d1..641932b74cea 100644
--- a/lib/routes/dedao/index.ts
+++ b/lib/routes/dedao/index.ts
@@ -33,7 +33,7 @@ async function handler(ctx) {
let items = (category === 'news' ? data.news : category === 'figure' ? data.figure : data.videoList).map((item) => ({
title: item.title,
pubDate: parseDate(item.online_time),
- link: `${rootUrl}/${category === 'news' ? 'article/' : category === 'figure' ? 'people/' : ''}${item.online_time.split('T')[0].split('-').join('')}/${item.token}`,
+ link: `${rootUrl}/${category === 'news' ? 'article/' : category === 'figure' ? 'people/' : ''}${item.online_time.split('T', 1)[0].split('-').join('')}/${item.token}`,
}));
items = await Promise.all(
diff --git a/lib/routes/dedao/knowledge.tsx b/lib/routes/dedao/knowledge.tsx
index 076b8e9a7ba4..d5eb7c28c293 100644
--- a/lib/routes/dedao/knowledge.tsx
+++ b/lib/routes/dedao/knowledge.tsx
@@ -5,7 +5,7 @@ import type { Route } from '@/types';
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
-const mentionPattern = /<\u2267\u2746>{"name":"(.*?)","uid":"\d+","at":"1"}<\/\u2266\u2746>/g;
+const mentionPattern = /<\u2267\u2746>\{"name":"(.*?)","uid":"\d+","at":"1"\}<\/\u2266\u2746>/g;
const formatNoteText = (text = '') => text.replaceAll('\n\n', '').replaceAll(mentionPattern, ' @$1');
diff --git a/lib/routes/dedao/list.ts b/lib/routes/dedao/list.ts
index ae6c6ad2daf9..9a1747174a53 100644
--- a/lib/routes/dedao/list.ts
+++ b/lib/routes/dedao/list.ts
@@ -45,7 +45,7 @@ async function handler(ctx) {
url: listUrl,
});
- const currentUrl = `${rootUrl}${listResponse.data.match(new RegExp('' + category + String.raw`<\/span><\/a>`))[1].split('"')[0]}`;
+ const currentUrl = `${rootUrl}${listResponse.data.match(new RegExp('' + category + String.raw`<\/span><\/a>`))[1].split('"', 1)[0]}`;
const currentResponse = await got({
method: 'get',
diff --git a/lib/routes/dedao/user.tsx b/lib/routes/dedao/user.tsx
index 164618039768..39ebb3772c8d 100644
--- a/lib/routes/dedao/user.tsx
+++ b/lib/routes/dedao/user.tsx
@@ -11,7 +11,7 @@ const types = {
12: '视频',
};
-const mentionPattern = /<\u2267\u2746>{"name":"(.*?)","uid":"\d+","at":"1"}<\/\u2266\u2746>/g;
+const mentionPattern = /<\u2267\u2746>\{"name":"(.*?)","uid":"\d+","at":"1"\}<\/\u2266\u2746>/g;
const formatNoteText = (text = '') => text.replaceAll('\n\n', '
').replaceAll(mentionPattern, ' @$1');
diff --git a/lib/routes/dehenglaw/index.ts b/lib/routes/dehenglaw/index.ts
index c5966d874ace..325ecf55d099 100644
--- a/lib/routes/dehenglaw/index.ts
+++ b/lib/routes/dehenglaw/index.ts
@@ -70,7 +70,7 @@ export const handler = async (ctx) => {
return {
title: $('title')
.text()
- .replace(/\|.*?$/, `| ${$('li.onthis').text()}`),
+ .replace(/\|.*$/, `| ${$('li.onthis').text()}`),
description: $('meta[name="Description"]').prop('content'),
link: currentUrl,
item: items,
diff --git a/lib/routes/denonbu/news.ts b/lib/routes/denonbu/news.ts
index e3617d38b926..4aa089a19ad2 100644
--- a/lib/routes/denonbu/news.ts
+++ b/lib/routes/denonbu/news.ts
@@ -170,7 +170,7 @@ async function handler(ctx: Context): Promise {
const { title, body, post_date, category, media } = item;
const link = buildLink(item);
const result: DataItem = {
- title: title ?? body.split('\n')[0],
+ title: title ?? body.split('\n', 1)[0],
description: body,
pubDate: timezone(parseDate(post_date), +9),
category: category.map((x) => x.name),
diff --git a/lib/routes/devolverdigital/blog.ts b/lib/routes/devolverdigital/blog.ts
index a601649eee20..d4669c061ce0 100644
--- a/lib/routes/devolverdigital/blog.ts
+++ b/lib/routes/devolverdigital/blog.ts
@@ -86,7 +86,7 @@ function parsePostImages($, content) {
const src = $img.attr('src') || '';
if (src.startsWith('/_next/image')) {
const srcSet = $img.attr('srcset') || '';
- const actualSrc = srcSet.split(',').pop()?.split(' ')[0] || src;
+ const actualSrc = srcSet.split(',').pop()?.split(' ', 1)[0] || src;
$img.attr('src', actualSrc);
}
$img.removeAttr('loading').removeAttr('decoding').removeAttr('data-nimg').removeAttr('style').removeAttr('sizes').removeAttr('srcset').removeAttr('referrerpolicy');
diff --git a/lib/routes/dgtle/article.ts b/lib/routes/dgtle/article.ts
index f88ee9b9fa55..fddfe07b35f4 100644
--- a/lib/routes/dgtle/article.ts
+++ b/lib/routes/dgtle/article.ts
@@ -31,12 +31,12 @@ export const handler = async (ctx: Context): Promise => {
const title: string | undefined = $(`div[data_cid="${id}"]`).text();
return {
- title: `${$('title').text().trim().split(/\s/)[0]}${title ? ` - ${title}` : id}`,
+ title: `${$('title').text().trim().split(/\s/, 1)[0]}${title ? ` - ${title}` : id}`,
description: $('meta[name="description"]').attr('content'),
link: targetUrl,
item: items,
allowEmpty: true,
- author: $('meta[name="keywords"]').attr('content')?.split(/,/)[0] ?? undefined,
+ author: $('meta[name="keywords"]').attr('content')?.split(/,/, 1)[0] ?? undefined,
language,
id: targetUrl,
};
diff --git a/lib/routes/dgtle/feed.ts b/lib/routes/dgtle/feed.ts
index 0989bff9c86b..106ce38e5b30 100644
--- a/lib/routes/dgtle/feed.ts
+++ b/lib/routes/dgtle/feed.ts
@@ -32,7 +32,7 @@ export const handler = async (ctx: Context): Promise => {
link: targetUrl,
item: items,
allowEmpty: true,
- author: $('meta[name="keywords"]').attr('content')?.split(/,/)[0] ?? undefined,
+ author: $('meta[name="keywords"]').attr('content')?.split(/,/, 1)[0] ?? undefined,
language,
id: targetUrl,
};
diff --git a/lib/routes/dgtle/news.ts b/lib/routes/dgtle/news.ts
index de5a2a7872a3..5f6f2324dcc8 100644
--- a/lib/routes/dgtle/news.ts
+++ b/lib/routes/dgtle/news.ts
@@ -26,12 +26,12 @@ export const handler = async (ctx: Context): Promise => {
const title: string | undefined = $(`div.whale_news_index-content-tab li[data_id="${id}"]`).text();
return {
- title: `${$('title').text().trim().split(/\s/)[0]}${title ? ` - ${title}` : id}`,
+ title: `${$('title').text().trim().split(/\s/, 1)[0]}${title ? ` - ${title}` : id}`,
description: $('meta[name="description"]').attr('content'),
link: targetUrl,
item: items,
allowEmpty: true,
- author: $('meta[name="keywords"]').attr('content')?.split(/,/)[0] ?? undefined,
+ author: $('meta[name="keywords"]').attr('content')?.split(/,/, 1)[0] ?? undefined,
language,
id: targetUrl,
};
diff --git a/lib/routes/dgtle/tag.ts b/lib/routes/dgtle/tag.ts
index 19fe571c530e..a4ee60c9149f 100644
--- a/lib/routes/dgtle/tag.ts
+++ b/lib/routes/dgtle/tag.ts
@@ -31,12 +31,12 @@ export const handler = async (ctx: Context): Promise => {
const title: string | undefined = $('div.tags-detail-top-1 h2').text();
return {
- title: `${$('title').text().trim().split(/\s/)[0]}${title ? ` - ${title}` : id}`,
+ title: `${$('title').text().trim().split(/\s/, 1)[0]}${title ? ` - ${title}` : id}`,
description: $('meta[name="description"]').attr('content'),
link: targetUrl,
item: items,
allowEmpty: true,
- author: $('meta[name="keywords"]').attr('content')?.split(/,/)[0] ?? undefined,
+ author: $('meta[name="keywords"]').attr('content')?.split(/,/, 1)[0] ?? undefined,
language,
id: targetUrl,
};
diff --git a/lib/routes/dgtle/video.ts b/lib/routes/dgtle/video.ts
index 1cb7c441c73c..1757e334dbe6 100644
--- a/lib/routes/dgtle/video.ts
+++ b/lib/routes/dgtle/video.ts
@@ -27,7 +27,7 @@ export const handler = async (ctx: Context): Promise => {
let items: DataItem[] = response.data.list.slice(0, limit).map((item): DataItem => {
const title: string = item.title;
- const image: string | undefined = item.cover?.split(/\?/)?.[0];
+ const image: string | undefined = item.cover?.split(/\?/, 1)?.[0];
const description: string | undefined = renderDescription({
images: image
? [
@@ -44,7 +44,7 @@ export const handler = async (ctx: Context): Promise => {
{
name: item.author.username,
url: undefined,
- avatar: item.author.avatar_path?.split(/\?/)?.[0],
+ avatar: item.author.avatar_path?.split(/\?/, 1)?.[0],
},
];
const guid = `dgtle-${item.id}`;
@@ -97,7 +97,7 @@ export const handler = async (ctx: Context): Promise => {
: undefined,
intro: $$('h3.video-summary').text(),
});
- const pubDateStr: string | undefined = $$('p.video-time').text()?.split(/\s/)?.[0];
+ const pubDateStr: string | undefined = $$('p.video-time').text()?.split(/\s/, 1)?.[0];
const linkUrl: string | undefined = $$('.title').attr('href');
const categoryEls: Element[] = $$('.category').toArray();
const categories: string[] = [...new Set(categoryEls.map((el) => $$(el).text()).filter(Boolean))];
@@ -156,10 +156,10 @@ export const handler = async (ctx: Context): Promise => {
})
);
- const author: string | undefined = $('meta[name="keywords"]').attr('content')?.split(/,/)[0] ?? undefined;
+ const author: string | undefined = $('meta[name="keywords"]').attr('content')?.split(/,/, 1)[0] ?? undefined;
return {
- title: $('title').text().trim().split(/\s/)[0],
+ title: $('title').text().trim().split(/\s/, 1)[0],
description: $('meta[name="description"]').attr('content'),
link: targetUrl,
item: items,
diff --git a/lib/routes/dianping/user.ts b/lib/routes/dianping/user.ts
index 6698f1aaeb0c..9f7cf14ab99f 100644
--- a/lib/routes/dianping/user.ts
+++ b/lib/routes/dianping/user.ts
@@ -75,7 +75,7 @@ async function handler(ctx) {
headerGeneratorOptions: PRESETS.MODERN_IOS,
});
- const nickNameReg = /window\.nickName = "(.*?)"/g;
+ const nickNameReg = /window\.nickName = "(.*?)"/;
const nickName = nickNameReg.exec(pageResponse as string)?.[1];
const response = await ofetch(`https://m.dianping.com/member/ajax/NobleUserFeeds?userId=${id}`, {
diff --git a/lib/routes/diershoubing/news.tsx b/lib/routes/diershoubing/news.tsx
index 765c2c830649..ff6758627e52 100644
--- a/lib/routes/diershoubing/news.tsx
+++ b/lib/routes/diershoubing/news.tsx
@@ -46,7 +46,7 @@ async function handler(ctx) {
acontent = contentData.imgs.split(',');
type = 'imgs';
} else {
- acontent = { img: contentData.video.split('|')[0], bvid: contentData.video.split('|')[1].replace('https://www.bilibili.com/video/', '') };
+ acontent = { img: contentData.video.split('|', 1)[0], bvid: contentData.video.split('|', 2)[1].replace('https://www.bilibili.com/video/', '') };
type = 'bilibili';
}
} else {
diff --git a/lib/routes/dingshao/share.ts b/lib/routes/dingshao/share.ts
index b234012bc0b7..71cda24f76e1 100644
--- a/lib/routes/dingshao/share.ts
+++ b/lib/routes/dingshao/share.ts
@@ -36,7 +36,7 @@ async function handler(ctx: Context) {
});
const items = response.value.bundle.channelMessages.map((message) => ({
- title: message.excerpt.split('\n')[0],
+ title: message.excerpt.split('\n', 1)[0],
description: message.content,
pubDate: parseDate(message.publishedAt),
category: message.tags,
diff --git a/lib/routes/discord/channel.ts b/lib/routes/discord/channel.ts
index 54642d0211bf..6fff42f61f67 100644
--- a/lib/routes/discord/channel.ts
+++ b/lib/routes/discord/channel.ts
@@ -49,7 +49,7 @@ async function handler(ctx) {
const { name: guildName, icon: guidIcon } = guildInfo;
const messages = messagesRaw.map((message) => ({
- title: message.content.split('\n')[0],
+ title: message.content.split('\n', 1)[0],
description: renderDescription({ message, guildInfo }),
author: `${message.author.global_name ?? message.author.username}(${message.author.username})`,
pubDate: parseDate(message.timestamp),
diff --git a/lib/routes/discord/quest.ts b/lib/routes/discord/quest.ts
index 0cbaa6e6263b..3955e263315f 100644
--- a/lib/routes/discord/quest.ts
+++ b/lib/routes/discord/quest.ts
@@ -48,7 +48,7 @@ async function handler() {
author: quest.config.messages.game_publisher,
pubDate: parseDate(quest.config.starts_at),
category: tasks,
- link: quest.config.application.link.split('?')[0],
+ link: quest.config.application.link.split('?', 1)[0],
guid: quest.id,
};
});
diff --git a/lib/routes/discord/search.ts b/lib/routes/discord/search.ts
index c04a046575b4..45ecac692d73 100644
--- a/lib/routes/discord/search.ts
+++ b/lib/routes/discord/search.ts
@@ -79,7 +79,7 @@ async function handler(ctx) {
}
const messages = searchResult.messages.flat().map((message) => ({
- title: message.content.split('\n')[0] || '(no content)',
+ title: message.content.split('\n', 1)[0] || '(no content)',
description: renderDescription({ message, guildInfo }),
author: message.author.global_name ?? message.author.username,
pubDate: parseDate(message.timestamp),
diff --git a/lib/routes/discuz/discuz.ts b/lib/routes/discuz/discuz.ts
index e39d4f063eed..427ef576d440 100644
--- a/lib/routes/discuz/discuz.ts
+++ b/lib/routes/discuz/discuz.ts
@@ -34,7 +34,7 @@ async function fetchWithAntiBot(url: string, header: Record) {
if (initialHtml.includes('document.location.reload()')) {
const setCookies = response.headers.getSetCookie?.() ?? [];
- const cookieStr = setCookies.map((c) => c.split(';')[0]).join('; ');
+ const cookieStr = setCookies.map((c) => c.split(';', 1)[0]).join('; ');
if (cookieStr) {
response = await ofetch.raw(url, {
method: 'get',
@@ -104,7 +104,7 @@ async function handler(ctx) {
// 若没有指定编码,则默认utf-8
const contentType = response.headers['content-type'] || '';
let $ = load(iconv.decode(responseData, 'utf-8'));
- const charset = contentType.match(/charset=([^;]*)/)?.[1] ?? $('meta[charset]').attr('charset') ?? $('meta[http-equiv="Content-Type"]').attr('content')?.split('charset=')?.[1];
+ const charset = contentType.match(/charset=([^;]*)/)?.[1] ?? $('meta[charset]').attr('charset') ?? $('meta[http-equiv="Content-Type"]').attr('content')?.split('charset=', 2)?.[1];
if (charset?.toLowerCase() !== 'utf-8') {
$ = load(iconv.decode(responseData, charset ?? 'utf-8'));
}
diff --git a/lib/routes/dlnews/category.tsx b/lib/routes/dlnews/category.tsx
index a30277817a10..d813b90c0731 100644
--- a/lib/routes/dlnews/category.tsx
+++ b/lib/routes/dlnews/category.tsx
@@ -69,7 +69,7 @@ const extractArticle = (item) =>
const { data: response } = await got(item.link);
const $ = load(response);
const scriptTagContent = $('script#fusion-metadata').text();
- const jsonData = JSON.parse(scriptTagContent.match(/Fusion\.globalContent=({.*?});Fusion\.globalContentConfig/)[1]).content_elements;
+ const jsonData = JSON.parse(scriptTagContent.match(/Fusion\.globalContent=(\{.*?\});Fusion\.globalContentConfig/)[1]).content_elements;
const filteredData = [];
for (const v of jsonData) {
if (v.type === 'header' && v.content.includes('What we’re reading')) {
diff --git a/lib/routes/dmzj/news.ts b/lib/routes/dmzj/news.ts
index a99b1594a00f..90fce6d40e1d 100644
--- a/lib/routes/dmzj/news.ts
+++ b/lib/routes/dmzj/news.ts
@@ -45,7 +45,7 @@ async function handler(ctx) {
.map((item) => ({
title: $(item).find('h3 a').text(),
link: $(item).find('h3 a').attr('href'),
- author: $(item).find('.head_con_p_o span:nth-child(3)').text().split(':')[1],
+ author: $(item).find('.head_con_p_o span:nth-child(3)').text().split(':', 2)[1],
pubDate: timezone(parseDate($(item).find('.head_con_p_o span').first().text(), 'YYYY-MM-DD HH:mm'), +8),
description: $(item).find('p.com_about').text(),
category: $(item)
diff --git a/lib/routes/dnaindia/common.ts b/lib/routes/dnaindia/common.ts
index e501d9b50378..180919d42009 100644
--- a/lib/routes/dnaindia/common.ts
+++ b/lib/routes/dnaindia/common.ts
@@ -44,7 +44,7 @@ export async function handler(ctx) {
.map((item) => $(item).find('a').text());
// Process date
const timeText = $('p.dna-update').text();
- const dateMatch = timeText.match(/Updated\s*:\s*([\w\s,:\d]+?)(?:\s*\||$)/);
+ const dateMatch = timeText.match(/Updated\s*:([\w\s,:]+)/);
let time = dateMatch ? dateMatch[1].trim() : '';
time = time.replace(/\s+IST$/, '');
const pubDate = timezone(parseDate(time), +5.5);
diff --git a/lib/routes/domp4/detail.ts b/lib/routes/domp4/detail.ts
index ddeaf4515008..a8500872b358 100644
--- a/lib/routes/domp4/detail.ts
+++ b/lib/routes/domp4/detail.ts
@@ -30,7 +30,7 @@ function getDomList($, detailUrl) {
export function getItemList($, detailUrl, second) {
const encoded = $('.article script[type]')
.text()
- .match(/return p}\('(.*)',(\d+),(\d+),'(.*)'.split\(/);
+ .match(/return p\}\('(.*)',(\d+),(\d+),'(.*)'.split\(/);
// 若 script 标签没有内容,直接解析 dom
if (!encoded) {
return getDomList($, detailUrl);
diff --git a/lib/routes/dongqiudi/utils.ts b/lib/routes/dongqiudi/utils.ts
index c927dd76828b..2b27887d450d 100644
--- a/lib/routes/dongqiudi/utils.ts
+++ b/lib/routes/dongqiudi/utils.ts
@@ -59,7 +59,7 @@ const ProcessImg = (content) => {
delete img.attribs['orig-src'];
delete img.attribs['data-src'];
}
- img.attribs.src = img.attribs.src.includes('?watermark') ? img.attribs.src.split('?watermark')[0] : img.attribs.src;
+ img.attribs.src = img.attribs.src.includes('?watermark') ? img.attribs.src.split('?watermark', 1)[0] : img.attribs.src;
});
};
@@ -145,7 +145,7 @@ const ProcessFeedType3 = (item, response) => {
const initialState = JSON.parse(
$('script:contains("window.__INITIAL_STATE__")')
.text()
- .match(/window\.__INITIAL_STATE__\s*=\s*(.*?);\(/)[1]
+ .match(/window\.__INITIAL_STATE__\s*=\s*((?:\S.*?)??);\(/)[1]
);
// filter out undefined item
diff --git a/lib/routes/dora-world/article.ts b/lib/routes/dora-world/article.ts
index c903ee1cce78..4c44d1392599 100644
--- a/lib/routes/dora-world/article.ts
+++ b/lib/routes/dora-world/article.ts
@@ -85,6 +85,6 @@ async function getContent(nextBuildId: string, contentId: string) {
content
.html()
?.replaceAll(rubyRegex, '$1($2)')
- ?.replaceAll(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFDCF\uFDE0-\uFFFD]/gm, '') ?? '';
+ ?.replaceAll(/[^\t\n\r\u0020-\uD7FF\uE000-\uFDCF\uFDE0-\uFFFD]/g, '') ?? '';
return description;
}
diff --git a/lib/routes/douban/other/doulist.ts b/lib/routes/douban/other/doulist.ts
index 77bf929e9fed..549083679dd5 100644
--- a/lib/routes/douban/other/doulist.ts
+++ b/lib/routes/douban/other/doulist.ts
@@ -52,7 +52,7 @@ async function handler(ctx) {
description = $(item).find('span.status-recommend-text').text().trim();
}
- if (type === '来自:豆瓣电影' || type === '来自:豆瓣' || type === '来自:豆瓣读书' || type === '来自:豆瓣音乐') {
+ if (['来自:豆瓣电影', '来自:豆瓣', '来自:豆瓣读书', '来自:豆瓣音乐'].includes(type)) {
title = $(item).find('div.bd.doulist-subject div.title a').text().trim();
link = $(item).find('div.bd.doulist-subject div.title a').attr('href');
diff --git a/lib/routes/douban/other/replied.ts b/lib/routes/douban/other/replied.ts
index 23767e6ce856..c231d97ee642 100644
--- a/lib/routes/douban/other/replied.ts
+++ b/lib/routes/douban/other/replied.ts
@@ -56,7 +56,7 @@ async function handler(ctx) {
method: 'get',
url: item.link,
});
- const match = detailResponse.data.match(/'comments':(.*)}],/);
+ const match = detailResponse.data.match(/'comments':(.*)\}\],/);
if (match.length > 1) {
const content = load(detailResponse.data);
diff --git a/lib/routes/douban/other/replies.ts b/lib/routes/douban/other/replies.ts
index 7d2515a6137a..711e7304063b 100644
--- a/lib/routes/douban/other/replies.ts
+++ b/lib/routes/douban/other/replies.ts
@@ -55,10 +55,10 @@ async function handler(ctx) {
url: item.link,
});
- const comments = JSON.parse(detailResponse.data.match(/'comments':(.*)}],/)[1] + '}]');
+ const comments = JSON.parse(detailResponse.data.match(/'comments':(.*)\}\],/)[1] + '}]');
for (const c of comments) {
- if (c.id === item.link.split('#')[1]) {
+ if (c.id === item.link.split('#', 2)[1]) {
return {
link: item.link,
title: `${c.author.name} 于 ${c.create_time} 的回应`,
diff --git a/lib/routes/douban/other/topic.ts b/lib/routes/douban/other/topic.ts
index e5c56ea4b36f..86c853bc9079 100644
--- a/lib/routes/douban/other/topic.ts
+++ b/lib/routes/douban/other/topic.ts
@@ -54,7 +54,7 @@ async function handler(ctx) {
let link;
let title;
if (type === 'status') {
- link = item.target.status.sharing_url.split('&')[0];
+ link = item.target.status.sharing_url.split('&', 1)[0];
author = item.target.status.author.name;
title = author + '的广播';
date = item.target.status.create_time;
diff --git a/lib/routes/douban/people/status.ts b/lib/routes/douban/people/status.ts
index f5a225d4fee4..1f9b89f6e591 100644
--- a/lib/routes/douban/people/status.ts
+++ b/lib/routes/douban/people/status.ts
@@ -101,7 +101,7 @@ function tryFixStatus(status) {
}
if (status.sharing_url) {
- status.sharing_url = status.sharing_url.split('&')[0];
+ status.sharing_url = status.sharing_url.split('&', 1)[0];
}
if (!result.isFixSuccess) {
diff --git a/lib/routes/douban/people/wish.ts b/lib/routes/douban/people/wish.ts
index bd676988bb78..7095ef18128d 100644
--- a/lib/routes/douban/people/wish.ts
+++ b/lib/routes/douban/people/wish.ts
@@ -34,7 +34,7 @@ async function handler(ctx) {
const userid = ctx.req.param('userid');
const routeParams = querystring.parse(ctx.req.param('routeParams'));
- let userName;
+ let username;
const pageSize = 15;
const pagesCount = routeParams.pagesCount ? Number.parseInt(routeParams.pagesCount) : 1;
@@ -63,7 +63,7 @@ async function handler(ctx) {
.then((data) => {
const $ = load(data);
const list = $('div.article > div.grid-view > div.item');
- userName = userName || $('div.side-info-txt > h3').text();
+ username = username || $('div.side-info-txt > h3').text();
if (list) {
return Promise.all(
@@ -92,7 +92,7 @@ async function handler(ctx) {
const items = (await Promise.all(tasks)).flat();
return {
- title: `豆瓣想看 - ${userName || userid}`,
+ title: `豆瓣想看 - ${username || userid}`,
link: `https://movie.douban.com/people/${userid}/wish`,
item: items,
};
diff --git a/lib/routes/douban/tv/coming.ts b/lib/routes/douban/tv/coming.ts
index 1b4581383b28..4f43b25e6972 100644
--- a/lib/routes/douban/tv/coming.ts
+++ b/lib/routes/douban/tv/coming.ts
@@ -55,7 +55,7 @@ const getPubDate = (pubdate?: string[]): Date | undefined => {
return undefined;
}
- const datePart = pubDateText.split('(')[0];
+ const datePart = pubDateText.split('(', 1)[0];
return parseDate(datePart);
};
@@ -65,7 +65,7 @@ const getSortTimestamp = (pubdate?: string[]): number => {
return Number.POSITIVE_INFINITY;
}
- const datePart = pubDateText.split('(')[0].trim();
+ const datePart = pubDateText.split('(', 1)[0].trim();
const match = /^(\d{4})(?:-(\d{1,2}))?(?:-(\d{1,2}))?/.exec(datePart);
if (!match) {
return Number.POSITIVE_INFINITY;
diff --git a/lib/routes/douyin/hashtag.ts b/lib/routes/douyin/hashtag.ts
index 47f3f01fa9e3..77541e05e406 100644
--- a/lib/routes/douyin/hashtag.ts
+++ b/lib/routes/douyin/hashtag.ts
@@ -47,12 +47,12 @@ async function handler(ctx) {
const tagData = await cache.tryGet(
`douyin:hashtag:${cid}`,
async () => {
- const browser = await playwright();
- const page = await browser.newPage();
- await page.setRequestInterception(true);
+ const context = await playwright();
+ const page = await context.newPage();
let awemeList = '';
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' || request.resourceType() === 'xhr' ? request.continue() : request.abort();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' || request.resourceType() === 'xhr' ? route.continue() : route.abort();
});
page.on('response', async (response) => {
const request = response.request();
@@ -61,11 +61,11 @@ async function handler(ctx) {
}
});
await page.goto(tagUrl, {
- waitUntil: 'networkidle2',
+ waitUntil: 'networkidle',
});
await page.waitForSelector('#RENDER_DATA');
const html = await page.evaluate(() => document.querySelector('#RENDER_DATA').textContent);
- await browser.close();
+ await context.close();
const renderData = JSON.parse(decodeURIComponent(html));
const dataKey = Object.keys(renderData).find((key) => renderData[key].topicDetail);
diff --git a/lib/routes/douyin/live.ts b/lib/routes/douyin/live.ts
index c1d3f7a438b4..93cb864a36f3 100644
--- a/lib/routes/douyin/live.ts
+++ b/lib/routes/douyin/live.ts
@@ -42,12 +42,11 @@ async function handler(ctx) {
`douyin:live:${rid}`,
async () => {
let roomInfo;
- const browser = await playwright();
- const page = await browser.newPage();
- await page.setRequestInterception(true);
-
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'stylesheet' || request.resourceType() === 'script' || request.resourceType() === 'xhr' ? request.continue() : request.abort();
+ const context = await playwright();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'stylesheet' || request.resourceType() === 'script' || request.resourceType() === 'xhr' ? route.continue() : route.abort();
});
page.on('response', async (response) => {
const request = response.request();
@@ -57,9 +56,9 @@ async function handler(ctx) {
});
logger.http(`Requesting ${pageUrl}`);
await page.goto(pageUrl, {
- waitUntil: 'networkidle2',
+ waitUntil: 'networkidle',
});
- await browser.close();
+ await context.close();
return roomInfo;
},
diff --git a/lib/routes/douyin/user.ts b/lib/routes/douyin/user.ts
index bbfbb8edc50f..ca0a020e8ed5 100644
--- a/lib/routes/douyin/user.ts
+++ b/lib/routes/douyin/user.ts
@@ -50,12 +50,11 @@ async function handler(ctx) {
`douyin:user:${uid}`,
async () => {
let postData;
- const browser = await playwright();
- const page = await browser.newPage();
- await page.setRequestInterception(true);
-
- page.on('request', (request) => {
- request.resourceType() === 'document' || request.resourceType() === 'script' || request.resourceType() === 'xhr' ? request.continue() : request.abort();
+ const context = await playwright();
+ const page = await context.newPage();
+ await page.route('**/*', (route) => {
+ const request = route.request();
+ request.resourceType() === 'document' || request.resourceType() === 'script' || request.resourceType() === 'xhr' ? route.continue() : route.abort();
});
page.on('response', async (response) => {
const request = response.request();
@@ -66,10 +65,10 @@ async function handler(ctx) {
logger.http(`Requesting ${pageUrl}`);
await page.goto(pageUrl, {
- waitUntil: 'networkidle2',
+ waitUntil: 'networkidle',
});
- await browser.close();
+ await context.close();
if (!postData) {
throw new Error('Empty post data. The request may be filtered by WAF.');
@@ -114,7 +113,7 @@ async function handler(ctx) {
const description = templates.desc({ desc, media });
return {
- title: post.desc.split('\n')[0],
+ title: post.desc.split('\n', 1)[0],
description,
link: `https://www.douyin.com/video/${post.aweme_id}`,
pubDate: parseDate(post.create_time * 1000),
diff --git a/lib/routes/dribbble/utils.tsx b/lib/routes/dribbble/utils.tsx
index c7438b556977..727b3836e658 100644
--- a/lib/routes/dribbble/utils.tsx
+++ b/lib/routes/dribbble/utils.tsx
@@ -17,7 +17,7 @@ async function loadContent(link) {
const shotData = JSON.parse(
$('script')
.text()
- .match(/shotData:\s({.+?}),\n/)?.[1] ?? '{}'
+ .match(/shotData:\s(\{.+?\}),\n/)?.[1] ?? '{}'
);
// Join multiple shots together by selecting elements with class 'media-shot' or 'main-shot' or 'block-media-wrapper'
@@ -51,11 +51,11 @@ async function loadContent(link) {
}
if (!img.attr('src') && img.data('src')) {
- img.attr('src', img.data('src').split('?')[0]);
+ img.attr('src', img.data('src').split('?', 1)[0]);
img.removeAttr('data-src');
}
- img.attr('src', img.attr('src').split('?')[0]);
+ img.attr('src', img.attr('src').split('?', 1)[0]);
img.removeAttr('srcset');
img.removeAttr('data-srcset');
});
diff --git a/lib/routes/duozhi/index.ts b/lib/routes/duozhi/index.ts
index 8f4358529458..c563b469b59c 100644
--- a/lib/routes/duozhi/index.ts
+++ b/lib/routes/duozhi/index.ts
@@ -45,7 +45,7 @@ export const handler = async (ctx: Context): Promise => {
]
: undefined,
});
- const pubDateStr: string | undefined = $el.find('div.post-attr').text().split(/\|/)[0]?.trim();
+ const pubDateStr: string | undefined = $el.find('div.post-attr').text().split(/\|/, 1)[0]?.trim();
const linkUrl: string | undefined = $aEl.attr('href');
const categoryEls: Element[] = $el.find('span.post-tag a.link-tag').toArray();
const categories: string[] = [...new Set(categoryEls.map((el) => $(el).text()).filter(Boolean))];
@@ -109,7 +109,7 @@ export const handler = async (ctx: Context): Promise => {
});
const pubDateStr: string | undefined = $$('div.subject-meta')
.text()
- ?.split(/发布/)[0];
+ ?.split(/发布/, 1)[0];
const categories: string[] = [
...new Set([
...(item.category ?? []),
diff --git a/lib/routes/dut/index.ts b/lib/routes/dut/index.ts
index 117bf63e663e..870fc93e1891 100644
--- a/lib/routes/dut/index.ts
+++ b/lib/routes/dut/index.ts
@@ -25,7 +25,7 @@ async function handler(ctx) {
let items;
let category = ctx.params[1] ?? (Object.hasOwn(defaults, site) ? defaults[site] : '');
- category = Object.hasOwn(shortcuts, site) ? (Object.hasOwn(shortcuts[site], category) ? shortcuts[site][category] : category) : category;
+ category = Object.hasOwn(shortcuts, site) && Object.hasOwn(shortcuts[site], category) ? shortcuts[site][category] : category;
const rootUrl = `https://${site}.dlut.edu.cn`;
const currentUrl = `${rootUrl}/${category}.htm`;
diff --git a/lib/routes/e-hentai/index.tsx b/lib/routes/e-hentai/index.tsx
index a426901f8793..fb5c2073ab74 100644
--- a/lib/routes/e-hentai/index.tsx
+++ b/lib/routes/e-hentai/index.tsx
@@ -50,7 +50,7 @@ async function handler(ctx) {
.toArray()
.map((tag) => $(tag).attr('title').replace(/^:/, '')),
description: needImages ? '' : `
`,
- enclosure_url: needTorrents ? (item.find('div.gldown a img[title="Show torrents"]').length > 0 ? item.find('.gldown a').attr('href') : undefined) : undefined,
+ enclosure_url: needTorrents && item.find('div.gldown a img[title="Show torrents"]').length > 0 ? item.find('.gldown a').attr('href') : undefined,
};
});
diff --git a/lib/routes/efe/index.ts b/lib/routes/efe/index.ts
new file mode 100644
index 000000000000..0c384b7d9222
--- /dev/null
+++ b/lib/routes/efe/index.ts
@@ -0,0 +1,97 @@
+import { load } from 'cheerio';
+import pMap from 'p-map';
+
+import type { Route } from '@/types';
+import cache from '@/utils/cache';
+import ofetch from '@/utils/ofetch';
+import { parseDate } from '@/utils/parse-date';
+
+const rootUrl = 'https://efe.com';
+
+const categories: Record = {
+ mundo: 'Mundo',
+ espana: 'España',
+ economia: 'Economía',
+ cultura: 'Cultura',
+ 'ciencia-y-tecnologia': 'Ciencia y Tecnología',
+ deportes: 'Deportes',
+ salud: 'Salud',
+ 'medio-ambiente': 'Medio Ambiente',
+ educacion: 'Educación',
+ 'euro-efe': 'EuroEFE',
+};
+
+export const route: Route = {
+ path: '/:category?',
+ name: 'Category',
+ maintainers: ['mlkgrnt'],
+ example: '/efe/mundo',
+ parameters: {
+ category: {
+ description: 'Category slug, see table below. Defaults to mundo.',
+ default: 'mundo',
+ },
+ },
+ handler,
+ categories: ['new-media'],
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: false,
+ supportBT: false,
+ supportPodcast: false,
+ supportScihub: false,
+ },
+ radar: [
+ {
+ source: ['efe.com/:category'],
+ target: '/:category',
+ },
+ ],
+};
+
+async function handler(ctx) {
+ const category = ctx.req.param('category') || 'mundo';
+ const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20;
+ const pageUrl = `${rootUrl}/${category}/`;
+
+ const response = await ofetch(pageUrl);
+ const $ = load(response);
+
+ const links = $('.e-loop-item .elementor-widget-theme-post-title a[href]')
+ .toArray()
+ .map((el) => $(el).attr('href'))
+ .filter((href): href is string => !!href && href.startsWith(`${rootUrl}/${category}/`) && /\/\d{4}-\d{2}-\d{2}\//.test(href));
+
+ const items = await pMap(
+ links.slice(0, limit),
+ (link) =>
+ cache.tryGet(link, async () => {
+ const detail = await ofetch(link);
+ const $detail = load(detail);
+
+ const title = $detail('title').text();
+ const dateMatch = detail.match(/"datePublished":\s*"([^"]+)"/);
+ const pubDate = dateMatch ? parseDate(dateMatch[1]) : undefined;
+
+ const image = $detail('meta[property="og:image"]').attr('content');
+ const content = $detail('.elementor-widget-theme-post-content');
+ content.find('.auto-banner').remove();
+ const description = (image ? `
` : '') + (content.html() || '');
+
+ return {
+ title,
+ link,
+ pubDate,
+ description,
+ };
+ }),
+ { concurrency: 2 }
+ );
+
+ return {
+ title: `EFE Noticias - ${categories[category] || category}`,
+ link: pageUrl,
+ item: items,
+ };
+}
diff --git a/lib/routes/efe/namespace.ts b/lib/routes/efe/namespace.ts
new file mode 100644
index 000000000000..55dcbb41ebee
--- /dev/null
+++ b/lib/routes/efe/namespace.ts
@@ -0,0 +1,7 @@
+import type { Namespace } from '@/types';
+
+export const namespace: Namespace = {
+ name: 'EFE Noticias',
+ url: 'efe.com',
+ lang: 'es',
+};
diff --git a/lib/routes/ehentai/ehapi.ts b/lib/routes/ehentai/ehapi.ts
index ba9284beaa7c..271f2fd86f62 100644
--- a/lib/routes/ehentai/ehapi.ts
+++ b/lib/routes/ehentai/ehapi.ts
@@ -161,7 +161,7 @@ function getBittorrent(cache, bittorrent_page_url) {
const match = onclick.match(/'(.*?)'/);
if (match) {
bittorrent_url = match[1];
- const match_p = bittorrent_url.match(/torrent\?p=(.*?)$/);
+ const match_p = bittorrent_url.match(/torrent\?p=(.*)$/);
if (match_p) {
p = match_p[1];
}
diff --git a/lib/routes/esquirehk/tag.tsx b/lib/routes/esquirehk/tag.tsx
index 21dcda81de0b..e15384ec2d66 100644
--- a/lib/routes/esquirehk/tag.tsx
+++ b/lib/routes/esquirehk/tag.tsx
@@ -96,7 +96,7 @@ const renderSubpages = (subpages): string =>
break;
}
case 'video_block': {
- const videoId = page.source?.split('&')[0];
+ const videoId = page.source?.split('&', 1)[0];
blocks.push(