style: migrate eslint to oxlint (#21224)

* chore: add oxlint support

* style: apply autofix

* style: apply autofix for toSorted

* style: use alternative api for nsfw-flag jsPlugins

* fix: git diff

* fix: codeql

* test: console.log

* style: update lint workflow to use Checkstyle SARIF converter

* style: remove debug log from extractDateFromURL function

* chore: use oxlint-json-to-sarif

* test: console.log

* test: remove console.log

* test: update cookie data expectation in getCookies test

https://github.com/mccutchen/go-httpbin/pull/235 breaks this

* style: type-aware linting

* style: enable type-aware linting for oxlint
This commit is contained in:
Tony 2026-02-28 21:23:11 +08:00 committed by GitHub
parent 75ff9aac67
commit 1acb805799
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
63 changed files with 1021 additions and 894 deletions

View File

@ -15,7 +15,7 @@ jobs:
eslint-warning:
name: Lint
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }}
runs-on: ubuntu-slim
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
security-events: write
@ -27,25 +27,21 @@ jobs:
node-version: lts/*
cache: 'pnpm'
- run: pnpm i
- name: Install ESLint SARIF formatter
# https://github.com/microsoft/sarif-js-sdk/issues/91 unncessary deps on eslint@8
run: |
wget https://raw.githubusercontent.com/microsoft/sarif-js-sdk/refs/heads/main/packages/eslint-formatter-sarif/sarif.js -O node_modules/sarif.cjs
pnpm i -D utf8 lodash jschardet
- name: Install oxlint to SARIF converter
run: pnpm i -g oxlint-json-to-sarif
- name: Lint
run: pnpm run lint
--format node_modules/sarif.cjs
--output-file eslint-results.sarif
run: pnpm exec oxlint --type-aware
--format=json | oxlint-json-to-sarif > oxlint-results.sarif
continue-on-error: true
- name: Upload analysis results to GitHub
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: eslint-results.sarif
sarif_file: oxlint-results.sarif
wait-for-processing: true
- name: Upload Artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
path: eslint-results.sarif
path: oxlint-results.sarif
# https://github.com/amannn/action-semantic-pull-request
title-lint:

View File

@ -6,7 +6,11 @@
"trailingComma": "es5",
"arrowParens": "always",
"ignorePatterns": ["lib/routes-deprecated", "lib/router.js", "babel.config.js", "scripts/docker/minify-docker.js", "dist", "pnpm-lock.yaml"],
"experimentalSortPackageJson": {
"sortPackageJson": {
"sortScripts": true
},
"sortImports": {
"groups": ["side_effect", "builtin", "external", ["internal", "subpath"], ["parent", "sibling", "index"], "style", "unknown"],
"order": "asc"
}
}

519
.oxlintrc.json Normal file
View File

@ -0,0 +1,519 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"categories": {
"correctness": "off"
},
"ignorePatterns": ["**/coverage", "**/.vscode", "**/docker-compose.yml", "!.github", "assets/build", "lib/routes-deprecated", "lib/router.js", "dist", "dist-lib", "dist-worker"],
"env": {
"builtin": true,
"browser": true,
"es2026": true,
"node": true
},
"plugins": ["eslint", "typescript", "node", "unicorn", "import"],
"jsPlugins": ["@stylistic/eslint-plugin", { "name": "js-import-x", "specifier": "eslint-plugin-import-x" }, { "name": "n", "specifier": "eslint-plugin-n" }, "./eslint-plugins/no-then.js", "./eslint-plugins/nsfw-flag.js"],
"rules": {
// #region --- ESLint js/recommended possible problems ---
"constructor-super": "error",
"for-direction": "error",
"getter-return": "error",
"no-async-promise-executor": "error",
"no-class-assign": "error",
"no-compare-neg-zero": "error",
"no-cond-assign": "error",
"no-const-assign": "error",
"no-constant-binary-expression": "error",
"no-constant-condition": "error",
// "no-control-regex": "error", -> off
"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-pattern": "error",
"no-ex-assign": "error",
"no-fallthrough": "error",
"no-func-assign": "error",
"no-import-assign": "error",
"no-invalid-regexp": "error",
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
"no-new-native-nonconstructor": "error",
"no-nonoctal-decimal-escape": "error",
"no-obj-calls": "error",
// "no-prototype-builtins": "error", -> off
"no-self-assign": "error",
"no-setter-return": "error",
"no-sparse-arrays": "error",
"no-this-before-super": "error",
"no-unassigned-vars": "error",
// "no-undef": "error",
"no-unexpected-multiline": "error",
"no-unreachable": "error",
"no-unsafe-finally": "error",
"no-unsafe-negation": "error",
"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",
"use-isnan": "error",
"valid-typeof": "error",
// #endregion
// #region --- ESLint js/recommended suggestions ---
"no-case-declarations": "error",
"no-delete-var": "error",
"no-empty": "error",
"no-empty-static-block": "error",
"no-extra-boolean-cast": "error",
"no-global-assign": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-shadow-restricted-names": "error",
"no-unused-labels": "error",
"no-useless-catch": "error",
"no-useless-escape": "error",
"no-with": "error",
"preserve-caught-error": "error",
"require-yield": "error",
// #endregion
// #region --- TypeScript flat/recommended ---
// "@typescript-eslint/ban-ts-comment": "error",
"no-array-constructor": "error", // equivalent to @typescript-eslint/no-array-constructor in oxlint
"@typescript-eslint/no-duplicate-enum-values": "error",
"@typescript-eslint/no-empty-object-type": "error",
// "@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-extra-non-null-assertion": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-asserted-optional-chain": "error",
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/no-this-alias": "error",
"@typescript-eslint/no-unnecessary-type-constraint": "error",
"@typescript-eslint/no-unsafe-declaration-merging": "error",
"@typescript-eslint/no-unsafe-function-type": "error",
// "no-unused-expressions": "off",
// "@typescript-eslint/no-unused-expressions": "error",
// "no-unused-vars": "off",
// "@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-wrapper-object-types": "error",
"@typescript-eslint/prefer-as-const": "error",
"@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/triple-slash-reference": "error",
// #endregion
// #region --- TypeScript flat/stylistic ---
"@typescript-eslint/adjacent-overload-signatures": "error",
// "@typescript-eslint/array-type": "error",
"@typescript-eslint/ban-tslint-comment": "error",
"@typescript-eslint/class-literal-property-style": "error",
"@typescript-eslint/consistent-generic-constructors": "error",
// "@typescript-eslint/consistent-indexed-object-style": "error",
"@typescript-eslint/consistent-type-assertions": "error",
// "@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/no-confusing-non-null-assertion": "error",
// "no-empty-function": "off",
// "@typescript-eslint/no-empty-function": "error",
// "@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/prefer-for-of": "error",
"@typescript-eslint/prefer-function-type": "error",
// #endregion
// #region --- n flat/recommended-script ---
"n/hashbang": "error",
// "n/no-deprecated-api": "error",
"node/no-exports-assign": "error",
"n/no-extraneous-import": "error",
// "n/no-extraneous-require": "error",
// "n/no-missing-import": "error",
// "n/no-missing-require": "error",
// "n/no-process-exit": "error",
"n/no-unpublished-bin": "error",
// "n/no-unpublished-import": "error",
// "n/no-unpublished-require": "error",
"n/no-unsupported-features/es-builtins": "error",
"n/no-unsupported-features/es-syntax": "error",
// "n/no-unsupported-features/node-builtins": "error",
"n/process-exit-as-throw": "error",
// #endregion
// #region --- unicorn ---
"unicorn/catch-error-name": "error",
"unicorn/consistent-assert": "error",
"unicorn/consistent-date-clone": "error",
"unicorn/consistent-empty-array-spread": "error",
"unicorn/consistent-existence-index-check": "error",
// "unicorn/consistent-function-scoping": "error",
"unicorn/empty-brace-spaces": "error",
"unicorn/error-message": "error",
"unicorn/escape-case": "error",
// "unicorn/expiring-todo-comments": "error", // not yet implemented
// "unicorn/explicit-length-check": "error",
// "unicorn/filename-case": "error",
// "unicorn/import-style": "error", // not yet implemented
// "unicorn/isolated-functions": "error", // not yet implemented
"unicorn/new-for-builtins": "error",
"unicorn/no-abusive-eslint-disable": "error",
"unicorn/no-accessor-recursion": "error",
"unicorn/no-anonymous-default-export": "error",
// "unicorn/no-array-callback-reference": "error",
"unicorn/no-array-for-each": "error",
"unicorn/no-array-method-this-argument": "error",
// "unicorn/no-array-reduce": "error",
"unicorn/no-array-reverse": "error",
// "unicorn/no-array-sort": "error",
// "unicorn/no-await-expression-member": "error",
"unicorn/no-await-in-promise-methods": "error",
"unicorn/no-console-spaces": "error",
"unicorn/no-document-cookie": "error",
// "unicorn/no-empty-file": "error",
// "unicorn/no-for-loop": "error", // won't be implemented
// "unicorn/no-hex-escape": "error",
"unicorn/no-immediate-mutation": "error",
"unicorn/no-instanceof-builtins": "error",
"unicorn/no-invalid-fetch-options": "error",
"unicorn/no-invalid-remove-event-listener": "error",
"unicorn/no-lonely-if": "error",
"unicorn/no-magic-array-flat-depth": "error",
// "unicorn/no-named-default": "error", -> use import/no-named-default
"no-negated-condition": "off",
"unicorn/no-negated-condition": "error",
"unicorn/no-negation-in-equality-check": "error",
"no-nested-ternary": "off",
// "unicorn/no-nested-ternary": "error",
"unicorn/no-new-array": "error",
"unicorn/no-new-buffer": "error",
// "unicorn/no-null": "error",
// "unicorn/no-object-as-default-parameter": "error",
// "unicorn/no-process-exit": "error",
"unicorn/no-single-promise-in-promise-methods": "error",
"unicorn/no-static-only-class": "error",
"unicorn/no-thenable": "error",
"unicorn/no-this-assignment": "error",
"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/no-unnecessary-polyfills": "error", // not yet implemented
"unicorn/no-unnecessary-slice-end": "error",
"unicorn/no-unreadable-array-destructuring": "error",
"unicorn/no-unreadable-iife": "error",
"unicorn/no-useless-collection-argument": "error",
"unicorn/no-useless-error-capture-stack-trace": "error",
"unicorn/no-useless-fallback-in-spread": "error",
"unicorn/no-useless-length-check": "error",
"unicorn/no-useless-promise-resolve-reject": "error",
"unicorn/no-useless-spread": "error",
// "unicorn/no-useless-switch-case": "error",
// "unicorn/no-useless-undefined": "error",
"unicorn/no-zero-fractions": "error",
// "unicorn/number-literal-case": "error",
// "unicorn/numeric-separators-style": "error",
"unicorn/prefer-add-event-listener": "error",
"unicorn/prefer-array-find": "error",
"unicorn/prefer-array-flat": "error",
"unicorn/prefer-array-flat-map": "error",
"unicorn/prefer-array-index-of": "error",
"unicorn/prefer-array-some": "error",
"unicorn/prefer-at": "error",
"unicorn/prefer-bigint-literals": "error",
"unicorn/prefer-blob-reading-methods": "error",
"unicorn/prefer-class-fields": "error",
"unicorn/prefer-classlist-toggle": "error",
// "unicorn/prefer-code-point": "error",
"unicorn/prefer-date-now": "error",
"unicorn/prefer-default-parameters": "error",
"unicorn/prefer-dom-node-append": "error",
"unicorn/prefer-dom-node-dataset": "error",
"unicorn/prefer-dom-node-remove": "error",
"unicorn/prefer-dom-node-text-content": "error",
"unicorn/prefer-event-target": "error",
// "unicorn/prefer-export-from": "error", // not yet implemented
// "unicorn/prefer-global-this": "error",
"unicorn/prefer-includes": "error",
"unicorn/prefer-keyboard-event-key": "error",
"unicorn/prefer-logical-operator-over-ternary": "error",
"unicorn/prefer-math-min-max": "error",
"unicorn/prefer-math-trunc": "error",
"unicorn/prefer-modern-dom-apis": "error",
"unicorn/prefer-modern-math-apis": "error",
// "unicorn/prefer-module": "error",
"unicorn/prefer-native-coercion-functions": "error",
"unicorn/prefer-negative-index": "error",
"unicorn/prefer-node-protocol": "error",
// "unicorn/prefer-number-properties": "error",
"unicorn/prefer-object-from-entries": "error",
"unicorn/prefer-optional-catch-binding": "error",
"unicorn/prefer-prototype-methods": "error",
"unicorn/prefer-query-selector": "error",
"unicorn/prefer-reflect-apply": "error",
"unicorn/prefer-regexp-test": "error",
"unicorn/prefer-response-static-json": "error",
"unicorn/prefer-set-has": "error",
"unicorn/prefer-set-size": "error",
// "unicorn/prefer-single-call": "error", // not yet implemented
// "unicorn/prefer-spread": "error",
"unicorn/prefer-string-raw": "error",
"unicorn/prefer-string-replace-all": "error",
// "unicorn/prefer-string-slice": "error",
"unicorn/prefer-string-starts-ends-with": "error",
"unicorn/prefer-string-trim-start-end": "error",
"unicorn/prefer-structured-clone": "error",
// "unicorn/prefer-switch": "error", // not yet implemented
"unicorn/prefer-ternary": "error",
// "unicorn/prefer-top-level-await": "error",
"unicorn/prefer-type-error": "error",
// "unicorn/prevent-abbreviations": "error", // not yet implemented
"unicorn/relative-url-style": "error",
"unicorn/require-array-join-separator": "error",
"unicorn/require-module-attributes": "error",
"unicorn/require-module-specifiers": "error",
"unicorn/require-number-to-fixed-digits-argument": "error",
// "unicorn/switch-case-braces": "error",
// "unicorn/template-indent": "error", // not yet implemented
// "unicorn/text-encoding-identifier-case": "error",
"unicorn/throw-new-error": "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
// #region --- suggestions ---
"arrow-body-style": "error",
"block-scoped-var": "error",
"curly": "error",
// "dot-notation": "error", -> use @typescript-eslint/dot-notation
"eqeqeq": "error",
"default-case": ["warn", { "commentPattern": "^no default$" }],
"default-case-last": "error",
"no-console": "error",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-label": "error",
"no-implicit-coercion": [
"error",
{
"boolean": false,
"number": false,
"string": false,
"disallowTemplateShorthand": true
}
],
// "no-implicit-globals": "error", // not yet implemented
"no-labels": "error",
"no-lonely-if": "error",
"no-multi-str": "error",
"no-new-func": "error",
"no-unneeded-ternary": "error",
"no-useless-computed-key": "error",
"no-useless-concat": "warn",
"no-useless-rename": "error",
"no-var": "error",
// "object-shorthand": "error", // not yet implemented
// "prefer-arrow-callback'": "error", // not yet implemented
"prefer-const": "error",
"prefer-object-has-own": "error",
"require-await": "error",
// "prefer-regex-literals": [ // not yet implemented
// "error",
// {
// "disallowRedundantWrapping": true
// }
// ],
// #endregion
// #region --- TypeScript ---
"@typescript-eslint/array-type": ["error", { "default": "array-simple" }],
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/consistent-indexed-object-style": "off", // stylistic
"@typescript-eslint/consistent-type-definitions": "off", // stylistic
"@typescript-eslint/no-empty-function": "off", // stylistic && tests
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-inferrable-types": ["error", { "ignoreParameters": true, "ignoreProperties": true }],
"@typescript-eslint/no-unnecessary-template-expression": "error", // type-aware
"@typescript-eslint/no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true }],
"@typescript-eslint/no-unused-vars": ["error", { "args": "after-used", "argsIgnorePattern": "^_" }],
// type-aware
// "@typescript-eslint/await-thenable": "off",
// "@typescript-eslint/no-base-to-string": "off",
// "@typescript-eslint/no-floating-promises": "off",
// "@typescript-eslint/no-misused-spread": "off",
// "@typescript-eslint/no-redundant-type-constituents": "off",
// "@typescript-eslint/unbound-method": "off",
// "@typescript-eslint/restrict-template-expressions": "off",
// #endregion
// #region --- unicorn ---
"unicorn/consistent-function-scoping": "warn",
"unicorn/explicit-length-check": "off",
"unicorn/filename-case": [
"error",
{
"case": "kebabCase",
"ignore": [".*\\.(yaml|yml)$", "RequestInProgress\\.js$"]
}
],
"unicorn/no-array-callback-reference": "warn",
"unicorn/no-array-reduce": "warn",
"unicorn/no-array-sort": "warn",
"unicorn/no-await-expression-member": "off",
"unicorn/no-empty-file": "warn",
// "unicorn/no-for-loop": "off", // won't be implemented
"unicorn/no-hex-escape": "warn",
"unicorn/no-nested-ternary": "off",
"unicorn/no-null": "off",
"unicorn/no-object-as-default-parameter": "warn",
"unicorn/no-process-exit": "off",
"unicorn/no-useless-switch-case": "off",
"unicorn/no-useless-undefined": ["error", { "checkArguments": false }],
"unicorn/number-literal-case": "off",
"unicorn/numeric-separators-style": [
"warn",
{
"onlyIfContainsSeparator": false,
"number": {
"minimumDigits": 7,
"groupLength": 3
},
"binary": {
"minimumDigits": 9,
"groupLength": 4
},
"octal": {
"minimumDigits": 9,
"groupLength": 4
},
"hexadecimal": {
"minimumDigits": 5,
"groupLength": 2
}
}
],
"unicorn/prefer-code-point": "warn",
"unicorn/prefer-global-this": "off",
"unicorn/prefer-import-meta-properties": "warn",
"unicorn/prefer-module": "off",
"unicorn/prefer-number-properties": ["error", { "checkInfinity": false, "checkNaN": false }],
"unicorn/prefer-spread": "warn",
"unicorn/prefer-string-slice": "warn",
// "unicorn/prefer-switch": [ // not yet implemented
// "warn",
// {
// "emptyDefaultCase": "do-nothing-comment"
// }
// ],
"unicorn/prefer-top-level-await": "off",
"unicorn/prevent-abbreviations": "off",
"unicorn/switch-case-braces": ["error", "avoid"],
"unicorn/text-encoding-identifier-case": "off",
// #endregion
// #region --- stylistic ---
"@stylistic/arrow-parens": "error",
"@stylistic/arrow-spacing": "error",
"@stylistic/comma-spacing": "error",
"@stylistic/comma-style": "error",
"@stylistic/function-call-spacing": "error",
"@stylistic/keyword-spacing": "off",
"@stylistic/linebreak-style": "error",
"@stylistic/lines-around-comment": ["error", { "beforeBlockComment": false }],
"@stylistic/no-multiple-empty-lines": "error",
"@stylistic/no-trailing-spaces": "error",
"@stylistic/rest-spread-spacing": "error",
"@stylistic/semi": "error",
"@stylistic/space-before-blocks": "error",
"@stylistic/space-in-parens": "error",
"@stylistic/space-infix-ops": "error",
"@stylistic/space-unary-ops": "error",
"@stylistic/spaced-comment": "error",
// #endregion
// #region --- import sorting ---
// oxfmt also handles import sorting
"sort-imports": "off",
"import-x/order": "off",
// "simple-import-sort/imports": "error",
// "simple-import-sort/exports": "error",
"import-x/first": "error",
"js-import-x/newline-after-import": "error", // oxc native not yet implemented
"no-duplicate-imports": "off",
"import-x/no-duplicates": "error",
"@typescript-eslint/consistent-type-imports": "error",
// #endregion
// #region --- n ---
"n/no-extraneous-require": "error",
"n/no-deprecated-api": "warn",
"n/no-missing-import": "off",
"n/no-missing-require": "off",
"n/no-process-exit": "off",
"n/no-unpublished-import": "off",
"n/no-unpublished-require": ["error", { "allowModules": ["tosource"] }],
"n/no-unsupported-features/node-builtins": [
"error",
{
"version": "^22.20.0 || ^24",
"allowExperimental": true,
"ignores": []
}
],
// #endregion
// github
"github/no-then": "warn",
// rsshub
"@rsshub/nsfw-flag/add-nsfw-flag": "error"
},
"overrides": [
{
"files": [".puppeteerrc.cjs"],
"plugins": ["typescript"],
"rules": {
"@typescript-eslint/no-require-imports": "off"
}
},
{
"files": ["**/*.test.ts"],
"plugins": ["typescript"],
"rules": {
"@typescript-eslint/no-unnecessary-template-expression": "off"
}
}
]
}

45
eslint-plugins/no-then.js Normal file
View File

@ -0,0 +1,45 @@
import { eslintCompatPlugin } from '@oxlint/plugins';
const rule = {
meta: {
type: 'suggestion',
docs: {
description: 'enforce using `async/await` syntax over Promises',
url: 'https://github.com/github/eslint-plugin-github/blob/main/docs/rules/no-then.md',
recommended: true,
},
schema: [],
messages: {
preferAsyncAwait: 'Prefer async/await to Promise.{{method}}()',
},
},
createOnce(context) {
return {
MemberExpression(node) {
if (node.property && node.property.name === 'then') {
context.report({
node: node.property,
messageId: 'preferAsyncAwait',
data: { method: 'then' },
});
} else if (node.property && node.property.name === 'catch') {
context.report({
node: node.property,
messageId: 'preferAsyncAwait',
data: { method: 'catch' },
});
}
},
};
},
};
export default eslintCompatPlugin({
meta: {
name: 'github',
},
rules: {
'no-then': rule,
},
});

View File

@ -1,6 +1,7 @@
/**
* ESLint 9 plugin to automatically mark NSFW routes with the nsfw flag
*/
import { eslintCompatPlugin } from '@oxlint/plugins';
const nsfwRoutes = [
'141jav',
@ -88,7 +89,7 @@ function isNsfwRoute(filePath) {
});
}
export default {
export default eslintCompatPlugin({
meta: {
name: '@rsshub/nsfw-flag',
version: '1.0.0',
@ -118,15 +119,14 @@ export default {
missingNsfwFlag: 'NSFW route is missing the nsfw flag in features',
},
},
create(context) {
const filename = context.filename || context.getFilename();
// 如果不是 NSFW 路由,跳过检查
if (!isNsfwRoute(filename)) {
return {};
}
createOnce(context) {
return {
before() {
// 如果不是 NSFW 路由,跳过检查
if (!isNsfwRoute(context.filename)) {
return false;
}
},
ExportNamedDeclaration(node) {
// 查找 export const route: Route = {...}
if (
@ -211,4 +211,4 @@ export default {
},
},
},
};
});

View File

@ -2,15 +2,15 @@ import js from '@eslint/js';
import stylistic from '@stylistic/eslint-plugin';
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import { defineConfig } from 'eslint/config';
import github from 'eslint-plugin-github';
import { importX } from 'eslint-plugin-import-x';
// import { importX } from 'eslint-plugin-import-x';
import n from 'eslint-plugin-n';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
// import simpleImportSort from 'eslint-plugin-simple-import-sort';
import unicorn from 'eslint-plugin-unicorn';
import eslintPluginYml from 'eslint-plugin-yml';
import { defineConfig } from 'eslint/config';
import globals from 'globals';
// import github from './eslint-plugins/no-then.js';
// import nsfwFlagPlugin from './eslint-plugins/nsfw-flag.js';
const SOURCE_FILES_GLOB = '**/*.?([cm])[jt]s?(x)';
@ -32,12 +32,12 @@ export default defineConfig([
plugins: {
'@stylistic': stylistic,
'@typescript-eslint': typescriptEslint,
github,
// github,
js,
n,
unicorn,
},
extends: [js.configs.recommended, typescriptEslint.configs['flat/recommended'], typescriptEslint.configs['flat/stylistic'], n.configs['flat/recommended-script'], unicorn.configs.recommended],
// extends: [js.configs.recommended, typescriptEslint.configs['flat/recommended'], typescriptEslint.configs['flat/stylistic'], n.configs['flat/recommended-script'], unicorn.configs.recommended],
languageOptions: {
globals: {
@ -50,33 +50,30 @@ export default defineConfig([
sourceType: 'module',
},
linterOptions: {
reportUnusedDisableDirectives: false,
},
rules: {
// #region possible problems
'array-callback-return': [
'error',
{
allowImplicit: true,
},
],
/*
'array-callback-return': ['error', { allowImplicit: true }],
'no-await-in-loop': 'error',
'no-control-regex': 'off',
'no-prototype-builtins': 'off',
*/
// #endregion
// #region suggestions
/*
'arrow-body-style': 'error',
'block-scoped-var': 'error',
curly: 'error',
'dot-notation': 'error',
eqeqeq: 'error',
'default-case': [
'warn',
{
commentPattern: '^no default$',
},
],
'default-case': ['warn', { commentPattern: '^no default$' }],
'default-case-last': 'error',
'no-console': 'error',
@ -99,7 +96,7 @@ export default defineConfig([
'no-lonely-if': 'error',
'no-multi-str': 'error',
'no-new-func': 'error',
*/
'no-restricted-syntax': [
'error',
{
@ -127,7 +124,7 @@ export default defineConfig([
message: 'Usage of .catch(() => {}) is not allowed. Please handle the error appropriately.',
},
],
/*
'no-unneeded-ternary': 'error',
'no-useless-computed-key': 'error',
'no-useless-concat': 'warn',
@ -146,9 +143,11 @@ export default defineConfig([
],
'require-await': 'error',
*/
// #endregion
// #region typescript
/*
'@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
'@typescript-eslint/ban-ts-comment': 'off',
@ -158,29 +157,13 @@ export default defineConfig([
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-inferrable-types': ['error', { ignoreParameters: true, ignoreProperties: true }],
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
},
],
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'after-used',
argsIgnorePattern: '^_',
},
],
'@typescript-eslint/prefer-for-of': 'error',
'@typescript-eslint/no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true }],
'@typescript-eslint/no-unused-vars': ['error', { args: 'after-used', argsIgnorePattern: '^_' }],
*/
// #endregion
// #region unicorn
/*
'unicorn/consistent-function-scoping': 'warn',
'unicorn/explicit-length-check': 'off',
@ -199,18 +182,15 @@ export default defineConfig([
'unicorn/no-empty-file': 'warn',
'unicorn/no-for-loop': 'off',
'unicorn/no-hex-escape': 'warn',
'unicorn/no-nested-ternary': 'off',
'unicorn/no-null': 'off',
'unicorn/no-object-as-default-parameter': 'warn',
'unicorn/no-nested-ternary': 'off',
'unicorn/no-process-exit': 'off',
'unicorn/no-useless-switch-case': 'off',
'unicorn/no-useless-undefined': [
'error',
{
checkArguments: false,
},
],
'unicorn/no-useless-undefined': ['error', { checkArguments: false }],
'unicorn/number-literal-case': 'off',
'unicorn/numeric-separators-style': [
'warn',
@ -244,13 +224,7 @@ export default defineConfig([
'unicorn/prefer-import-meta-properties': 'warn',
'unicorn/prefer-module': 'off',
'unicorn/prefer-number-properties': [
'error',
{
checkInfinity: false,
checkNaN: false,
},
],
'unicorn/prefer-number-properties': ['error', { checkInfinity: false, checkNaN: false }],
'unicorn/prefer-spread': 'warn',
'unicorn/prefer-string-slice': 'warn',
@ -266,10 +240,11 @@ export default defineConfig([
'unicorn/prevent-abbreviations': 'off',
'unicorn/switch-case-braces': ['error', 'avoid'],
'unicorn/text-encoding-identifier-case': 'off',
'unicorn/number-literal-case': 'off',
*/
// #endregion
// #region stylistic
/*
'@stylistic/arrow-parens': 'error',
'@stylistic/arrow-spacing': 'error',
'@stylistic/comma-spacing': 'error',
@ -278,12 +253,7 @@ export default defineConfig([
'@stylistic/keyword-spacing': 'off',
'@stylistic/linebreak-style': 'error',
'@stylistic/lines-around-comment': [
'error',
{
beforeBlockComment: false,
},
],
'@stylistic/lines-around-comment': ['error', { beforeBlockComment: false }],
'@stylistic/no-multiple-empty-lines': 'error',
'@stylistic/no-trailing-spaces': 'error',
@ -294,23 +264,19 @@ export default defineConfig([
'@stylistic/space-infix-ops': 'error',
'@stylistic/space-unary-ops': 'error',
'@stylistic/spaced-comment': 'error',
*/
// #endregion
// #region node specific rules
/*
'n/no-extraneous-require': 'error',
'n/no-deprecated-api': 'warn',
'n/no-missing-import': 'off',
'n/no-missing-require': 'off',
'n/no-process-exit': 'off',
'n/no-unpublished-import': 'off',
'n/no-unpublished-require': [
'error',
{
allowModules: ['tosource'],
},
],
'n/no-unpublished-require': ['error', { allowModules: ['tosource'] }],
'n/no-unsupported-features/node-builtins': [
'error',
@ -320,10 +286,11 @@ export default defineConfig([
ignores: [],
},
],
*/
// #endregion
// github
'github/no-then': 'warn',
// 'github/no-then': 'warn',
},
},
{
@ -333,6 +300,7 @@ export default defineConfig([
},
},
{
/*
files: [SOURCE_FILES_GLOB],
plugins: {
'simple-import-sort': simpleImportSort,
@ -351,7 +319,7 @@ export default defineConfig([
'@typescript-eslint/consistent-type-imports': 'error',
'import-x/consistent-type-specifier-style': ['error', 'prefer-top-level'],
},
},*/
},
{
files: ['**/*.yaml', '**/*.yml'],
@ -361,29 +329,13 @@ export default defineConfig([
},
language: 'yml/yaml',
rules: {
'lines-around-comment': [
'error',
{
beforeBlockComment: false,
},
],
'lines-around-comment': ['error', { beforeBlockComment: false }],
'yml/indent': [
'error',
4,
{
indicatorValueIndent: 2,
},
],
'yml/indent': ['error', 4, { indicatorValueIndent: 2 }],
'yml/no-empty-mapping-value': 'off',
'yml/quotes': [
'error',
{
prefer: 'single',
},
],
'yml/quotes': ['error', { prefer: 'single' }],
},
},
]);

View File

@ -97,7 +97,7 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
if (item.link) {
let baseUrl = data.link;
if (baseUrl && !/^https?:\/\//.test(baseUrl)) {
baseUrl = /^\/\//.test(baseUrl) ? 'http:' + baseUrl : 'http://' + baseUrl;
baseUrl = baseUrl.startsWith('//') ? 'http:' + baseUrl : 'http://' + baseUrl;
}
item.link = new URL(item.link, baseUrl).href;
@ -109,7 +109,7 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
let baseUrl = item.link || data.link;
if (baseUrl && !/^https?:\/\//.test(baseUrl)) {
baseUrl = /^\/\//.test(baseUrl) ? 'http:' + baseUrl : 'http://' + baseUrl;
baseUrl = baseUrl.startsWith('//') ? 'http:' + baseUrl : 'http://' + baseUrl;
}
$('script').remove();

View File

@ -18,7 +18,6 @@ export const route: Route = {
function extractDateFromURL(url: string) {
const regex = /\d{4}-\d{2}-\d{2}/;
const match = url.match(regex);
return match ? match[0] : null;
}

View File

@ -169,7 +169,7 @@ async function handler(ctx) {
const downloadLink = content('#read_tpc').first().find('a').last();
const copyLink = content('#copytext')?.first()?.text();
if (downloadLink?.text()?.startsWith('http') && /bt\.azvmw\.com$/.test(new URL(downloadLink.text()).hostname)) {
if (new URL(downloadLink.text()).hostname === 'bt.azvmw.com') {
const torrentResponse = await ofetch(downloadLink.text());
const torrent = load(torrentResponse);

View File

@ -1,5 +1,6 @@
import { raw } from 'hono/html';
import { renderToString } from 'hono/jsx/dom/server';
import type { JSX } from 'hono/jsx/jsx-runtime';
type DescriptionData = {
image?: {

View File

@ -47,34 +47,31 @@ async function handler() {
items = items.filter((item) => item.availableSizes.length !== 0);
const list = items.map((item) => {
const imgUrl = JSON.parse(item.imageUrls).front;
const originalPrice = getUSDPrice(item.originalPrice);
const regearPrice = item.priceRange[0] === item.priceRange[1] ? getUSDPrice(item.priceRange[0]) : `${getUSDPrice(item.priceRange[0])} - ${getUSDPrice(item.priceRange[1])}`;
const data = {
title: item.displayTitle,
link: item.pdpLink.url,
imgUrl: JSON.parse(item.imageUrls).front,
availableSizes: item.availableSizes,
color: item.color,
originalPrice: getUSDPrice(item.originalPrice),
regearPrice: item.priceRange[0] === item.priceRange[1] ? getUSDPrice(item.priceRange[0]) : `${getUSDPrice(item.priceRange[0])} - ${getUSDPrice(item.priceRange[1])}`,
description: '',
description: renderToString(
<div>
Available Sizes:&nbsp;
{item.availableSizes.map((size) => (
<>{size}&nbsp;</>
))}
<br />
Color: {item.color}
<br />
Original Price: {originalPrice}
<br />
Regear Price: {regearPrice}
<br />
<img src={imgUrl} />
<br />
<br />
</div>
),
};
data.description = renderToString(
<div>
Available Sizes:&nbsp;
{data.availableSizes.map((size) => (
<>{size}&nbsp;</>
))}
<br />
Color: {data.color}
<br />
Original Price: {data.originalPrice}
<br />
Regear Price: {data.regearPrice}
<br />
<img src={data.imgUrl} />
<br />
<br />
</div>
);
return data;
});
@ -82,10 +79,6 @@ async function handler() {
title: 'Arcteryx - Regear - New Arrivals',
link: url,
description: 'Arcteryx - Regear - New Arrivals',
item: list.map((item) => ({
title: item.title,
link: item.link,
description: item.description,
})),
item: list,
};
}

View File

@ -49,7 +49,7 @@ export interface Work {
rank_date: string;
term: string;
}> | null;
rate_average_2dp: number | number;
rate_average_2dp: number;
rate_count: number;
rate_count_detail: Array<{
count: number;

View File

@ -474,8 +474,7 @@ export type DynamicType =
* https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/dynamic/dynamic_enum.md#%E5%8A%A8%E6%80%81%E4%B8%BB%E4%BD%93%E7%B1%BB%E5%9E%8B
*/
export type MajorType =
| 'MAJOR_TYPE_NONE' // 动态失效, 示例: 716510857084796964
| 'MAJOR_TYPE_NONE' // 转发动态, 示例: 866756840240709701
| 'MAJOR_TYPE_NONE' // 动态失效, 示例: 716510857084796964 转发动态, 示例: 866756840240709701
| 'MAJOR_TYPE_OPUS' // 图文动态, 示例: 870176712256651305
| 'MAJOR_TYPE_ARCHIVE' // 视频, 示例: 716526237365829703
| 'MAJOR_TYPE_PGC' // 剧集更新, 示例: 645981661420322824

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/prefer-code-point */
import CryptoJS from 'crypto-js';
import { config } from '@/config';

View File

@ -1,3 +1,5 @@
// oxlint-disable unicorn/prefer-math-trunc
// oxlint-disable no-unused-vars
/* eslint-disable prefer-rest-params */
/* eslint-disable default-case */
/* eslint-disable unicorn/consistent-function-scoping */

View File

@ -64,7 +64,7 @@ async function handler(ctx) {
const url = item.attr('href');
if (url.startsWith('http')) {
link = url;
} else if (/^\//.test(url)) {
} else if (url.startsWith('/')) {
link = `${rootUrl}${url}`;
} else {
link = `${currentUrl}/${url}`;

View File

@ -5,7 +5,7 @@ import got from '@/utils/got';
import { renderArticle } from './templates/article';
const parseArticle = async (item) => {
if (/\.blog\.caixin\.com$/.test(new URL(item.link).hostname)) {
if (new URL(item.link).hostname.endsWith('.blog.caixin.com')) {
return parseBlogArticle(item);
} else {
const { data: response } = await got(item.link);

View File

@ -2,6 +2,7 @@ import { load } from 'cheerio';
import type { DataItem } from '@/types';
import cache from '@/utils/cache';
import logger from '@/utils/logger';
import ofetch from '@/utils/ofetch';
export const baseUrl = 'https://www.capitalmind.in';

View File

@ -104,8 +104,7 @@ async function handler(ctx) {
$(
$('div.col-xs-8 span')
.toArray()
.filter((a) => $(a).text().startsWith('来源'))
?.pop()
.findLast((a) => $(a).text().startsWith('来源'))
)
?.text()
?.split(//)

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/prefer-code-point */
import { inflateSync } from 'node:zlib';
const unzip = (b64Data) => {

View File

@ -66,7 +66,7 @@ async function handler(ctx) {
}
const renderDescription = (item): string => {
const fullText = item.metadata?.full_text ? item.metadata.full_text.replaceAll(/\n/gm, '<br>') : '';
const fullText = item.metadata?.full_text ? item.metadata.full_text.replaceAll('\n', '<br>') : '';
const firstComment = item.comments?.length ? item.comments[0].text.slice(0, 100) : '';
return renderToString(

View File

@ -65,7 +65,7 @@ const processItems = async (apiUrl, limit, tryGet, ...params) => {
return {
title: item.title,
link: /^\/\//.test(item.url) ? `https:${item.url}` : item.url,
link: item.url.startsWith('//') ? `https:${item.url}` : item.url,
description: item.description,
category: [item.category_name, ...(item.tags?.split(',') ?? [])],
guid: item.content_id,

View File

@ -13,7 +13,7 @@ function fixUrl(itemLink, baseUrl) {
// 处理相对链接
if (itemLink) {
if (baseUrl && !/^https?:\/\//.test(baseUrl)) {
baseUrl = /^\/\//.test(baseUrl) ? 'http:' + baseUrl : 'http://' + baseUrl;
baseUrl = baseUrl.startsWith('//') ? 'http:' + baseUrl : 'http://' + baseUrl;
}
itemLink = new URL(itemLink, baseUrl).href;
}

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/prefer-code-point */
import { config } from '@/config';
import ConfigNotFoundError from '@/errors/types/config-not-found';

View File

@ -26,7 +26,7 @@ async function handler(ctx) {
const { type = '' } = ctx.req.param();
const referer = `https://m.douban.com/book/${type}`;
const _ = async (type) => {
const requestItem = async (type) => {
const response = await got({
url: `https://m.douban.com/rexxar/api/v2/subject_collection/book_${type}/items?start=0&count=10`,
headers: { Referer: referer },
@ -34,7 +34,7 @@ async function handler(ctx) {
return response.data.subject_collection_items;
};
const items = type ? await _(type) : [...(await _('fiction')), ...(await _('nonfiction'))];
const items = type ? await requestItem(type) : [...(await requestItem('fiction')), ...(await requestItem('nonfiction'))];
return {
title: `豆瓣热门图书-${type ? (type === 'fiction' ? '虚构类' : '非虚构类') : '全部'}`,

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/prefer-code-point */
import aesjs from 'aes-js';
import { renderToString } from 'hono/jsx/dom/server';

View File

@ -2,6 +2,7 @@ import * as cheerio from 'cheerio';
import { destr } from 'destr';
import { raw } from 'hono/html';
import { renderToString } from 'hono/jsx/dom/server';
import type { JSX } from 'hono/jsx/jsx-runtime';
import type { Route } from '@/types';
import cache from '@/utils/cache';

View File

@ -74,7 +74,7 @@ async function handler(ctx: Context): Promise<Data> {
};
}
const getUrlIcon = (url: string, fallback?: boolean | undefined) => {
const getUrlIcon = (url: string, fallback?: boolean) => {
let src: string;
let fallbackUrl = '';

View File

@ -41,7 +41,7 @@ async function handler(ctx) {
return {
title: item.text(),
pubDate: parseDate(pubDate),
link: /\.html$/.test(link) ? link : `${link}#${pubDate}`,
link: link.endsWith('.html') ? link : `${link}#${pubDate}`,
};
});

View File

@ -46,7 +46,7 @@ async function handler(ctx) {
return {
title: item.text(),
link: `${rootUrl}${/^\.\.\/\.\./.test(item.attr('href')) ? item.attr('href').replace(/^\.\.\/\.\./, '') : `/xwdt/${category}${item.attr('href').replace(/^\./, '')}`}`,
link: `${rootUrl}${item.attr('href').startsWith('../..') ? item.attr('href').replace(/^\.\.\/\.\./, '') : `/xwdt/${category}${item.attr('href').replace(/^\./, '')}`}`,
};
});

View File

@ -47,7 +47,7 @@ async function handler(ctx) {
const items = await Promise.all(
data.items.map((item) => {
if (/^https:\/\/www\.nmpa\.gov\.cn\//.test(item.link)) {
if (item.link.startsWith('https://www.nmpa.gov.cn/')) {
return cache.tryGet(item.link, async () => {
const { data: html } = await got(item.link);
const $ = load(html);
@ -55,7 +55,7 @@ async function handler(ctx) {
item.pubDate = timezone(parseDate($('meta[name="PubDate"]').attr('content')), +8);
return item;
});
} else if (/^https:\/\/mp\.weixin\.qq\.com\//.test(item.link)) {
} else if (item.link.startsWith('https://mp.weixin.qq.com/')) {
return finishArticleItem(item);
} else {
return item;

View File

@ -30,7 +30,7 @@ async function handler(ctx) {
}
const rootUrl = 'https://www.nsfc.gov.cn';
const currentUrl = new URL((/\/more$/.test(thePath) ? `${thePath}.htm` : thePath) || 'publish/portal0/tab442/', rootUrl).href;
const currentUrl = new URL((thePath.endsWith('/more') ? `${thePath}.htm` : thePath) || 'publish/portal0/tab442/', rootUrl).href;
const { data: response } = await got(currentUrl);

View File

@ -5,6 +5,7 @@ import type { Context } from 'hono';
import type { Data, DataItem, Route } from '@/types';
import { ViewType } from '@/types';
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/prefer-code-point */
import { load } from 'cheerio';
import type { Route } from '@/types';

View File

@ -262,7 +262,7 @@ const generateSignature = () => {
* @param {Set<number>} visited - Set of visited indices to prevent infinite loops.
* @returns {unknown} - The resolved value.
*/
const resolveNuxtData = (arr: unknown[], index: number, visited: Set<number> = new Set()): unknown => {
const resolveNuxtData = (arr: unknown[], index: number, visited = new Set<number>()): unknown => {
if (visited.has(index)) {
return arr[index];
}

View File

@ -73,7 +73,7 @@ export const route: Route = {
};
async function handler(ctx) {
const isWestern = /^\/western/.test(getSubPath(ctx));
const isWestern = getSubPath(ctx).startsWith('/western');
const domain = ctx.req.query('domain') ?? 'javbus.com';
const westernDomain = ctx.req.query('western_domain') ?? 'javbus.org';

View File

@ -136,7 +136,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
content_html: $$el.find('div.content-card__body').html(),
};
})
.filter((link): link is { url: string; type: string; content_html: string } => true);
.filter((_link): _link is { url: string; type: string; content_html: string } => true);
const description: string = renderDescription({
description: cleanHtml($$('div.page-section').eq(1).html() ?? $$('div.copy-block').html() ?? '', ['div.richtext p', 'h3', 'h4', 'h5', 'h6', 'figure', 'img', 'ul', 'li', 'span', 'b']),

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/prefer-code-point */
const text_tag = {
LINE_BREAK: 0,
INLINE_CODE: 1,
@ -68,6 +69,7 @@ class Ucs2Text {
return new Ucs2Text(_start === _end ? '' : this.codePoints.slice(_start, _end));
}
slice(a, b) {
// oxlint-disable-next-line unicorn/prefer-string-slice
return this.substring(a, b).toString();
}
toString() {

View File

@ -71,7 +71,7 @@ const getMangaMeta = async (id: string, needCover: boolean = true, lang?: string
* @usage const mangaMetaMap = await getMangaMetaByIds(['f98660a1-d2e2-461c-960d-7bd13df8b76d']);
*/
export async function getMangaMetaByIds(ids: string[], needCover: boolean = true, lang?: string | string[]): Promise<Map<string, { id: string; title: string; description: string; cover?: string }>> {
const deDuplidatedIds = [...new Set(ids)].sort();
const deDuplidatedIds = [...new Set(ids)].toSorted();
const includes = needCover ? ['cover_art'] : [];
const rawMangaMetas = (await cache.tryGet(

View File

@ -105,7 +105,7 @@ interface MisskeyFile {
thumbnailUrl: string | null;
comment: string | null;
folderId: string | null;
folder?: unknown | null;
folder?: unknown;
userId: string | null;
user?: MisskeyUser | null;
}

View File

@ -57,7 +57,7 @@ async function handler() {
const res = await fetch(link);
const $ = load(res);
const isTV = /^\/TV/.test(new URL(link).pathname);
const isTV = new URL(link).pathname.startsWith('/TV');
return {
title,

View File

@ -10,12 +10,7 @@ import { parseDate } from '@/utils/parse-date';
async function loadContent(link) {
const data = await ofetch(link);
const $ = load(data);
const dtStr = $('.content-title-area')
.find('h6')
.first()
.text()
.replaceAll(/&nbsp;/gi, ' ')
.trim();
const dtStr = $('.content-title-area').find('h6').first().text().replaceAll('&nbsp;', ' ').trim();
$('.splide__arrows, .slide-control, [class^="ad-"], style').remove();

View File

@ -73,7 +73,7 @@ async function handler(ctx) {
const items = response.con.map((item) => {
let enclosure_url = item.playUrlHigh ?? item.playUrlMedium ?? item.playUrlLow ?? item.playUrl;
enclosure_url = /\.m3u8$/.test(enclosure_url) ? item.downloadUrl : enclosure_url;
enclosure_url = enclosure_url.endsWith('.m3u8') ? item.downloadUrl : enclosure_url;
const fileExt = new URL(enclosure_url).pathname.split('.').pop();
const enclosure_type = fileExt ? `audio/${audio_types[fileExt]}` : '';

View File

@ -72,7 +72,7 @@ async function handler(ctx) {
const items = data.map((item) => {
let enclosure_url = item.playUrlHigh ?? item.playUrlLow;
enclosure_url = /\.m3u8$/.test(enclosure_url) ? item.downloadUrl : enclosure_url;
enclosure_url = enclosure_url.endsWith('.m3u8') ? item.downloadUrl : enclosure_url;
const file_ext = new URL(enclosure_url).pathname.split('.').pop();
const enclosure_type = file_ext ? `audio/${audio_types[file_ext]}` : '';

View File

@ -1,6 +1,7 @@
import { load } from 'cheerio';
import { raw } from 'hono/html';
import { renderToString } from 'hono/jsx/dom/server';
import type { JSX } from 'hono/jsx/jsx-runtime';
import type { Route } from '@/types';
import { ViewType } from '@/types';

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/prefer-code-point */
const decodeCFEmail = (encoded) => {
const parseHex = (string, position) => Number.parseInt(string.slice(position, position + 2), 16);
let decoded = '';

View File

@ -22,7 +22,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
const $: CheerioAPI = load(response);
const language: string = $('html').attr('lang') ?? 'en';
const data: string | undefined = response.match(/window\.__DATA__=JSON\.parse\(`(.*?)`\)/)?.[1];
const parsedData = data ? JSON.parse(data.replaceAll('\\\\', '\\')) : undefined;
const parsedData = data ? JSON.parse(data.replaceAll(String.raw`\\`, '\\')) : undefined;
let items: DataItem[] = parsedData
? parsedData.initialData.props.results.slice(0, limit).map((item): DataItem => {
@ -101,7 +101,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
const detailResponse = await ofetch(item.link);
const detailData: string | undefined = detailResponse.match(/window\.__DATA__=JSON\.parse\(`(.*?)`\)/)?.[1];
const parsedDetailData = detailData ? JSON.parse(detailData.replaceAll('\\\\', '\\')) : undefined;
const parsedDetailData = detailData ? JSON.parse(detailData.replaceAll(String.raw`\\`, '\\')) : undefined;
if (!parsedDetailData) {
return item;

View File

@ -1,5 +1,6 @@
import { raw } from 'hono/html';
import { renderToString } from 'hono/jsx/dom/server';
import type { JSX } from 'hono/jsx/jsx-runtime';
type DescriptionImage = {
src?: string;

View File

@ -68,7 +68,7 @@ function sortThumb(thumb: Api.TypePhotoSize) {
}
function chooseLargestThumb(thumbs: Api.TypePhotoSize[]) {
thumbs = [...thumbs].sort((a, b) => sortThumb(a) - sortThumb(b));
thumbs = [...thumbs].toSorted((a, b) => sortThumb(a) - sortThumb(b));
return thumbs.pop();
}

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/prefer-code-point */
import md5 from '@/utils/md5';
const SALT = '1Ftjv0bfpVmqbE38';

View File

@ -53,7 +53,7 @@ async function handler(ctx) {
const { data: res } = await got(item.link);
const $ = cheerio.load(res);
if (/^https:\/\/tonglinv\.pixnet\.net/.test(item.link)) {
if (item.link.startsWith('https://tonglinv.pixnet.net/')) {
item.description = $('.article-content-inner').html();
} else if (/^https?:\/\/blog\.xuite\.net\//.test(item.link)) {
item.description = $('#content_all').html();

View File

@ -1,3 +1,4 @@
// oxlint-disable no-undef
/* eslint-disable unicorn/prefer-spread */
/* eslint-disable unicorn/prefer-math-trunc */
// @ts-nocheck

View File

@ -411,7 +411,7 @@ const ProcessFeed = (ctx, { data = [] }, params = {}) => {
(isRetweet && {
links: [
{
url: `https://x.com/${item.user?.screen_name || userScreenName}/status/${item.conversation_id_str}`,
url: `https://x.com/${item.user?.screen_name}/status/${item.conversation_id_str}`,
type: 'repost',
},
],

View File

@ -4,6 +4,7 @@ import timezone from 'dayjs/plugin/timezone.js';
import utc from 'dayjs/plugin/utc.js';
import { raw } from 'hono/html';
import { renderToString } from 'hono/jsx/dom/server';
import type { JSX } from 'hono/jsx/jsx-runtime';
import { FetchError } from 'ofetch';
import type { Route } from '@/types';

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/prefer-code-point */
// Credit:
// https://blog.csdn.net/zjq592767809/article/details/126512798
// https://blog.csdn.net/zhoumi_/article/details/126659351

View File

@ -1,5 +1,6 @@
import { load } from 'cheerio';
import { renderToString } from 'hono/jsx/dom/server';
import type { JSX } from 'hono/jsx/jsx-runtime';
import type { Route } from '@/types';
import cache from '@/utils/cache';

View File

@ -85,7 +85,7 @@ async function handler(ctx) {
items = await Promise.all(
items
.filter((a) => (id === 'jnyb' ? /\?div=1$/.test(a) : true))
.filter((a) => (id === 'jnyb' ? a.endsWith('?div=1') : true))
.slice(0, limit)
.map((link) =>
cache.tryGet(link, async () => {

View File

@ -266,11 +266,7 @@ Unknown paragraph
),
http.get(`https://mp.weixin.qq.com/s/rsshub_test_redirect_no_location`, () => HttpResponse.text('', { status: 302 })),
http.get(`https://mp.weixin.qq.com/s/rsshub_test_recursive_redirect`, () => HttpResponse.redirect(`https://mp.weixin.qq.com/s/rsshub_test_recursive_redirect`)),
http.get(`http://rsshub.test/headers`, ({ request }) =>
HttpResponse.json({
...Object.fromEntries(request.headers.entries()),
})
),
http.get(`http://rsshub.test/headers`, ({ request }) => HttpResponse.json(Object.fromEntries(request.headers.entries()))),
http.post(`http://rsshub.test/form-post`, async ({ request }) => {
const formData = await request.formData();
return HttpResponse.json({

View File

@ -1,3 +1,4 @@
// oxlint-disable unicorn/prefer-math-trunc
// xxhash-wasm shim for Cloudflare Workers
// Uses Web Crypto API instead of WebAssembly

View File

@ -86,7 +86,7 @@ describe('puppeteer-utils', () => {
waitUntil: 'domcontentloaded',
});
const data = await page.evaluate(() => JSON.parse(document.body.textContent || ''));
expect(data).toEqual(Object.fromEntries(cookieArrayExampleCom.map(({ name, value }) => [name, value])));
expect(data.cookies).toEqual(Object.fromEntries(cookieArrayExampleCom.map(({ name, value }) => [name, value])));
}, 45000);
it('setCookies & getCookies example.org', async () => {

View File

@ -35,10 +35,11 @@
"container-deploy": "npm run container-build && wrangler deploy --config wrangler-container.toml --containers-rollout=immediate",
"dev": "cross-env NODE_ENV=dev NODE_OPTIONS='--max-http-header-size=32768' tsx watch --inspect --clear-screen=false lib/index.ts",
"dev:cache": "cross-env NODE_ENV=production NODE_OPTIONS='--max-http-header-size=32768' tsx watch --clear-screen=false lib/index.ts",
"format": "eslint --cache --fix \"**/*.{ts,tsx,js,yml}\" --concurrency auto && oxfmt .",
"format:check": "eslint --cache \"**/*.{ts,tsx,js,yml}\" --concurrency auto && oxfmt . --check",
"eslint": "eslint --cache . --concurrency auto",
"format": "oxlint --type-aware --fix \"**/*.{ts,tsx,js,yml}\" && oxfmt .",
"format:check": "oxlint --type-aware \"**/*.{ts,tsx,js,yml}\" && oxfmt . --check",
"format:staged": "lint-staged",
"lint": "eslint --cache . --concurrency auto",
"lint": "oxlint --type-aware .",
"prepare": "husky || true",
"prepublishOnly": "npm run build:lib",
"profiling": "cross-env NODE_ENV=production tsx --prof lib/index.ts",
@ -148,6 +149,7 @@
"@cloudflare/workers-types": "4.20260305.0",
"@eslint/eslintrc": "3.3.4",
"@eslint/js": "10.0.1",
"@oxlint/plugins": "1.50.0",
"@stylistic/eslint-plugin": "5.9.0",
"@types/aes-js": "3.1.4",
"@types/babel__preset-env": "7.10.0",
@ -174,7 +176,6 @@
"domhandler": "5.0.3",
"eslint": "10.0.2",
"eslint-nibble": "9.1.1",
"eslint-plugin-github": "6.0.0",
"eslint-plugin-import-x": "4.16.1",
"eslint-plugin-n": "17.24.0",
"eslint-plugin-simple-import-sort": "12.1.1",
@ -191,6 +192,8 @@
"msw": "2.4.3",
"node-network-devtools": "1.0.29",
"oxfmt": "0.35.0",
"oxlint": "1.50.0",
"oxlint-tsgolint": "0.15.0",
"remark-parse": "11.0.0",
"supertest": "7.2.2",
"tsdown": "0.20.3",
@ -206,6 +209,7 @@
"oxfmt --no-error-on-unmatched-pattern"
],
"*.{ts,tsx,js,yml}": [
"oxlint --type-aware --fix",
"eslint --cache --fix --concurrency auto",
"oxfmt --no-error-on-unmatched-pattern"
]
@ -235,10 +239,6 @@
"wrangler"
],
"overrides": {
"array-includes": "npm:@nolyfill/array-includes@^1",
"array.prototype.findlastindex": "npm:@nolyfill/array.prototype.findlastindex@^1",
"array.prototype.flat": "npm:@nolyfill/array.prototype.flat@^1",
"array.prototype.flatmap": "npm:@nolyfill/array.prototype.flatmap@^1",
"difflib": "https://codeload.github.com/postlight/difflib.js/tar.gz/32e8e38c7fcd935241b9baab71bb432fd9b166ed",
"es-set-tostringtag": "npm:@nolyfill/es-set-tostringtag@^1",
"google-play-scraper>got": "^14.6.4",
@ -246,19 +246,12 @@
"google-play-scraper>tough-cookie": "^6.0.0",
"hasown": "npm:@nolyfill/hasown@^1",
"is-core-module": "npm:@nolyfill/is-core-module@^1",
"object.assign": "npm:@nolyfill/object.assign@^1",
"object.fromentries": "npm:@nolyfill/object.fromentries@^1",
"object.groupby": "npm:@nolyfill/object.groupby@^1",
"object.values": "npm:@nolyfill/object.values@^1",
"request>form-data": "^2.5.5",
"rss-parser@3.13.0>entities": "^7.0.0",
"rss-parser@3.13.0>xml2js": "^0.6.2",
"safe-buffer": "npm:@nolyfill/safe-buffer@^1",
"safe-regex-test": "npm:@nolyfill/safe-regex-test@^1",
"safer-buffer": "npm:@nolyfill/safer-buffer@^1",
"side-channel": "npm:@nolyfill/side-channel@^1",
"string.prototype.includes": "npm:@nolyfill/string.prototype.includes@^1",
"string.prototype.trimend": "npm:@nolyfill/string.prototype.trimend@^1"
"side-channel": "npm:@nolyfill/side-channel@^1"
},
"patchedDependencies": {
"rss-parser@3.13.0": "patches/rss-parser@3.13.0.patch"

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,7 @@ const foloAnalysis = await (
).json();
const foloAnalysisResult = foloAnalysis.data as Record<string, { subscriptionCount: number; topFeeds: any[] }>;
const foloAnalysisTop100 = Object.entries(foloAnalysisResult)
.sort((a, b) => b[1].subscriptionCount - a[1].subscriptionCount)
.toSorted((a, b) => b[1].subscriptionCount - a[1].subscriptionCount)
.slice(0, 150);
const __dirname = getCurrentPath(import.meta.url);