diff --git a/.github/update.log b/.github/update.log index 049e459977..dae274939f 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1054,3 +1054,4 @@ Update On Mon Jul 7 20:39:12 CEST 2025 Update On Tue Jul 8 20:39:34 CEST 2025 Update On Wed Jul 9 20:39:55 CEST 2025 Update On Thu Jul 10 20:38:34 CEST 2025 +Update On Fri Jul 11 20:40:18 CEST 2025 diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index c2b357519c..f1f85aeba8 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -58,9 +58,9 @@ "@iconify/json": "2.2.357", "@monaco-editor/react": "4.7.0", "@tanstack/react-query": "5.82.0", - "@tanstack/react-router": "1.125.6", - "@tanstack/react-router-devtools": "1.125.6", - "@tanstack/router-plugin": "1.125.6", + "@tanstack/react-router": "1.127.0", + "@tanstack/react-router-devtools": "1.127.0", + "@tanstack/router-plugin": "1.127.0", "@tauri-apps/plugin-clipboard-manager": "2.2.2", "@tauri-apps/plugin-dialog": "2.2.2", "@tauri-apps/plugin-fs": "2.3.0", @@ -87,7 +87,7 @@ "unplugin-auto-import": "19.3.0", "unplugin-icons": "22.1.0", "validator": "13.15.15", - "vite": "7.0.3", + "vite": "7.0.4", "vite-plugin-html": "3.2.2", "vite-plugin-sass-dts": "1.3.31", "vite-plugin-svgr": "4.3.0", diff --git a/clash-nyanpasu/frontend/ui/package.json b/clash-nyanpasu/frontend/ui/package.json index 258ce22077..c9579af1a0 100644 --- a/clash-nyanpasu/frontend/ui/package.json +++ b/clash-nyanpasu/frontend/ui/package.json @@ -30,7 +30,7 @@ "react-i18next": "15.6.0", "react-use": "17.6.0", "tailwindcss": "4.1.11", - "vite": "7.0.3", + "vite": "7.0.4", "vite-tsconfig-paths": "5.1.4" }, "devDependencies": { diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index b9f3ffe546..e5f834a306 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -2,7 +2,7 @@ "manifest_version": 1, "latest": { "mihomo": "v1.19.11", - "mihomo_alpha": "alpha-ce2675a", + "mihomo_alpha": "alpha-fb464bb", "clash_rs": "v0.8.1", "clash_premium": "2023-09-05-gdcc8d87", "clash_rs_alpha": "0.8.1-alpha+sha.86171e1" @@ -69,5 +69,5 @@ "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" } }, - "updated_at": "2025-07-08T22:21:31.499Z" + "updated_at": "2025-07-10T22:21:15.443Z" } diff --git a/clash-nyanpasu/package.json b/clash-nyanpasu/package.json index 0f4df8b384..e53fd4a393 100644 --- a/clash-nyanpasu/package.json +++ b/clash-nyanpasu/package.json @@ -65,11 +65,11 @@ "@tauri-apps/cli": "2.5.0", "@types/fs-extra": "11.0.4", "@types/lodash-es": "4.17.12", - "@types/node": "22.15.33", + "@types/node": "22.16.3", "@typescript-eslint/eslint-plugin": "8.36.0", "@typescript-eslint/parser": "8.36.0", "autoprefixer": "10.4.21", - "conventional-changelog-conventionalcommits": "9.0.0", + "conventional-changelog-conventionalcommits": "9.1.0", "cross-env": "7.0.3", "dedent": "1.6.0", "eslint": "9.30.1", diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index 5ef36e9ce3..67b5b00e9d 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -21,7 +21,7 @@ importers: devDependencies: '@commitlint/cli': specifier: 19.8.1 - version: 19.8.1(@types/node@22.15.33)(typescript@5.8.3) + version: 19.8.1(@types/node@22.16.3)(typescript@5.8.3) '@commitlint/config-conventional': specifier: 19.8.1 version: 19.8.1 @@ -44,8 +44,8 @@ importers: specifier: 4.17.12 version: 4.17.12 '@types/node': - specifier: 22.15.33 - version: 22.15.33 + specifier: 22.16.3 + version: 22.16.3 '@typescript-eslint/eslint-plugin': specifier: 8.36.0 version: 8.36.0(@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) @@ -56,8 +56,8 @@ importers: specifier: 10.4.21 version: 10.4.21(postcss@8.5.6) conventional-changelog-conventionalcommits: - specifier: 9.0.0 - version: 9.0.0 + specifier: 9.1.0 + version: 9.1.0 cross-env: specifier: 7.0.3 version: 7.0.3 @@ -102,7 +102,7 @@ importers: version: 16.3.0 knip: specifier: 5.61.3 - version: 5.61.3(@types/node@22.15.33)(typescript@5.8.3) + version: 5.61.3(@types/node@22.16.3)(typescript@5.8.3) lint-staged: specifier: 16.1.2 version: 16.1.2 @@ -247,7 +247,7 @@ importers: version: 4.1.11 '@tanstack/router-zod-adapter': specifier: 1.81.5 - version: 1.81.5(@tanstack/react-router@1.125.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(zod@3.25.76) + version: 1.81.5(@tanstack/react-router@1.127.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(zod@3.25.76) '@tauri-apps/api': specifier: 2.5.0 version: 2.5.0 @@ -346,14 +346,14 @@ importers: specifier: 5.82.0 version: 5.82.0(react@19.1.0) '@tanstack/react-router': - specifier: 1.125.6 - version: 1.125.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: 1.127.0 + version: 1.127.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tanstack/react-router-devtools': - specifier: 1.125.6 - version: 1.125.6(@tanstack/react-router@1.125.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.125.4)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.5)(tiny-invariant@1.3.3) + specifier: 1.127.0 + version: 1.127.0(@tanstack/react-router@1.127.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.127.0)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.5)(tiny-invariant@1.3.3) '@tanstack/router-plugin': - specifier: 1.125.6 - version: 1.125.6(@tanstack/react-router@1.125.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) + specifier: 1.127.0 + version: 1.127.0(@tanstack/react-router@1.127.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) '@tauri-apps/plugin-clipboard-manager': specifier: 2.2.2 version: 2.2.2 @@ -389,13 +389,13 @@ importers: version: 13.15.2 '@vitejs/plugin-legacy': specifier: 7.0.0 - version: 7.0.0(terser@5.36.0)(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 7.0.0(terser@5.36.0)(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) '@vitejs/plugin-react': specifier: 4.6.0 - version: 4.6.0(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 4.6.0(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) '@vitejs/plugin-react-swc': specifier: 3.10.2 - version: 3.10.2(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 3.10.2(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) change-case: specifier: 5.4.4 version: 5.4.4 @@ -433,20 +433,20 @@ importers: specifier: 13.15.15 version: 13.15.15 vite: - specifier: 7.0.3 - version: 7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) + specifier: 7.0.4 + version: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) vite-plugin-html: specifier: 3.2.2 - version: 3.2.2(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 3.2.2(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) vite-plugin-sass-dts: specifier: 1.3.31 - version: 1.3.31(postcss@8.5.6)(prettier@3.6.2)(sass-embedded@1.89.2)(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 1.3.31(postcss@8.5.6)(prettier@3.6.2)(sass-embedded@1.89.2)(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) vite-plugin-svgr: specifier: 4.3.0 - version: 4.3.0(rollup@4.40.0)(typescript@5.8.3)(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 4.3.0(rollup@4.40.0)(typescript@5.8.3)(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) vite-tsconfig-paths: specifier: 5.1.4 - version: 5.1.4(typescript@5.8.3)(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 5.1.4(typescript@5.8.3)(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) zod: specifier: 3.25.76 version: 3.25.76 @@ -482,7 +482,7 @@ importers: version: 19.1.8 '@vitejs/plugin-react': specifier: 4.6.0 - version: 4.6.0(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 4.6.0(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) ahooks: specifier: 3.9.0 version: 3.9.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -511,11 +511,11 @@ importers: specifier: 4.1.11 version: 4.1.11 vite: - specifier: 7.0.3 - version: 7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) + specifier: 7.0.4 + version: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) vite-tsconfig-paths: specifier: 5.1.4 - version: 5.1.4(typescript@5.8.3)(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 5.1.4(typescript@5.8.3)(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) devDependencies: '@emotion/react': specifier: 11.14.0 @@ -540,7 +540,7 @@ importers: version: 5.1.0(typescript@5.8.3) vite-plugin-dts: specifier: 4.5.4 - version: 4.5.4(@types/node@22.15.33)(rollup@4.40.0)(typescript@5.8.3)(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 4.5.4(@types/node@22.16.3)(rollup@4.40.0)(typescript@5.8.3)(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)) scripts: dependencies: @@ -2933,16 +2933,16 @@ packages: peerDependencies: react: ^18 || ^19 - '@tanstack/react-router-devtools@1.125.6': - resolution: {integrity: sha512-3+Np/HPQ1jpdai58xY6fScnjJz08iIG6aKhCVVy8tfPC76jTyXc+ygqM9wtUt3kJYy9/Lf7j3dAJlbtxF+TIXg==} + '@tanstack/react-router-devtools@1.127.0': + resolution: {integrity: sha512-43TNDJqgEDiLhzWkn4XW8oDItp4pod6USWX1gkqCX/8g3uCkzM+VXwRKMjWk2pouNrTMwp7H/WiWp/nn2vfC0w==} engines: {node: '>=12'} peerDependencies: - '@tanstack/react-router': ^1.125.6 + '@tanstack/react-router': ^1.127.0 react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-router@1.125.6': - resolution: {integrity: sha512-znyUGTq+WRhXwToNTYiluUBLjMdQVxz+ZQ9Ep2PBuS9O+3Qm3kaM7n64hA84ISoCtLqMwTo7Ofw0W4WeLdjpYg==} + '@tanstack/react-router@1.127.0': + resolution: {integrity: sha512-MxGpMfM7swT8b+7Y9S8bmRbzOQXiTcPhXKwETQuDrctqOVyl2m9OTqGCJpFCVUn7oD+RqmQ7KBS+N0zgENStJg==} engines: {node: '>=12'} peerDependencies: react: '>=18.0.0 || >=19.0.0' @@ -2967,15 +2967,15 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/router-core@1.125.4': - resolution: {integrity: sha512-tdgGI0Kwt3Lgs9ceLbG32NPh4I2H1T9t2TKjcS+I78sifm5rjTWV8lfqVRNrvMPk5ek60vXPOL2AHAUg6ohwsA==} + '@tanstack/router-core@1.127.0': + resolution: {integrity: sha512-hHgbtLOAnN61LFqBrE2bq3mctRLfXvJefBlTFakZJavSoMEniX6bMQ5ZMDwMtpo57Hbyzx2rTD4yZfYu74Eydg==} engines: {node: '>=12'} - '@tanstack/router-devtools-core@1.125.4': - resolution: {integrity: sha512-5QbCQCcJcN/M0NF2TARKqauJ8QeRuk7kyHQMCqOoSJWWGUVcDHEmcbg1ZCJevfPVZPgnUjV9mqDfCPTYWT8/+w==} + '@tanstack/router-devtools-core@1.127.0': + resolution: {integrity: sha512-K/UFaru0sVonaRoqQFUoNiqDt4AvXLxcRd2+9HjbGSC1xckAUNEEAcVl7jwQfERg89e9IVfHiPOIuDdQFjFBtA==} engines: {node: '>=12'} peerDependencies: - '@tanstack/router-core': ^1.125.4 + '@tanstack/router-core': ^1.127.0 csstype: ^3.0.10 solid-js: '>=1.9.5' tiny-invariant: ^1.3.3 @@ -2983,16 +2983,16 @@ packages: csstype: optional: true - '@tanstack/router-generator@1.125.4': - resolution: {integrity: sha512-jF71znMvpZxmkQF0MxfjKKyvXtft9NWRCVcLhb+6d/8nrVGNiEw+dsXn/CLpeRQLk7Mg/fsp/WipBql1dd3Qaw==} + '@tanstack/router-generator@1.127.0': + resolution: {integrity: sha512-k6f2Ekkoe4MB8lEeMfefQ4031yVf+3BIArsQqFB3HOzJaIRzPo8w8Boq7XqwZ2/r1lwSpK0z/xrNLcF2EMmeaw==} engines: {node: '>=12'} - '@tanstack/router-plugin@1.125.6': - resolution: {integrity: sha512-SWfp++tkjb0grVqa/Xdvi9QAs93e9/fZMBZ6q0fvvQruMyciCmjWyE/qV3tS/Qh0WZdzIRP6yl8Gha2Lin4q1w==} + '@tanstack/router-plugin@1.127.0': + resolution: {integrity: sha512-PphNi7PVf1PGLFD5fkrFQBLfknJNsug0jVJ7Mr4ChkhRsPSKs0hJtUPr9FxENSgY9TGbDTJaWw+42e5xfC2vUw==} engines: {node: '>=12'} peerDependencies: '@rsbuild/core': '>=1.0.2' - '@tanstack/react-router': ^1.125.6 + '@tanstack/react-router': ^1.127.0 vite: '>=5.0.0 || >=6.0.0' vite-plugin-solid: ^2.11.2 webpack: '>=5.92.0' @@ -3326,8 +3326,8 @@ packages: '@types/node@16.18.108': resolution: {integrity: sha512-fj42LD82fSv6yN9C6Q4dzS+hujHj+pTv0IpRR3kI20fnYeS0ytBpjFO9OjmDowSPPt4lNKN46JLaKbCyP+BW2A==} - '@types/node@22.15.33': - resolution: {integrity: sha512-wzoocdnnpSxZ+6CjW4ADCK1jVmd1S/J3ArNWfn8FDDQtRm8dkDg7TA+mvek2wNrfCgwuZxqEOiB9B1XCJ6+dbw==} + '@types/node@22.16.3': + resolution: {integrity: sha512-sr4Xz74KOUeYadexo1r8imhRtlVXcs+j3XK3TcoiYk7B1t3YRVJgtaD3cwX73NYb71pmVuMLNRhJ9XKdoDB74g==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -4166,8 +4166,8 @@ packages: resolution: {integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==} engines: {node: '>=16'} - conventional-changelog-conventionalcommits@9.0.0: - resolution: {integrity: sha512-5e48V0+DsWvQBEnnbBFhYQwYDzFPXVrakGPP1uSxekDkr5d7YWrmaWsgJpKFR0SkXmxK6qQr9O42uuLb9wpKxA==} + conventional-changelog-conventionalcommits@9.1.0: + resolution: {integrity: sha512-MnbEysR8wWa8dAEvbj5xcBgJKQlX/m0lhS8DsyAAWDHdfs2faDJxTgzRYlRYpXSe7UiKrIIlB4TrBKU9q9DgkA==} engines: {node: '>=18'} conventional-commits-parser@5.0.0: @@ -7500,10 +7500,20 @@ packages: peerDependencies: seroval: ^1.0 + seroval-plugins@1.3.2: + resolution: {integrity: sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + seroval@1.2.1: resolution: {integrity: sha512-yBxFFs3zmkvKNmR0pFSU//rIsYjuX418TnlDmc2weaq5XFDqDIV/NOMPBoLrbxjLH42p4UzRuXHryXh9dYcKcw==} engines: {node: '>=10'} + seroval@1.3.2: + resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} + engines: {node: '>=10'} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -8330,8 +8340,8 @@ packages: vite: optional: true - vite@7.0.3: - resolution: {integrity: sha512-y2L5oJZF7bj4c0jgGYgBNSdIu+5HF+m68rn2cQXFbGoShdhV1phX9rbnxy9YXj82aS8MMsCLAAFkRxZeWdldrQ==} + vite@7.0.4: + resolution: {integrity: sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -9591,11 +9601,11 @@ snapshots: '@bufbuild/protobuf@2.5.2': {} - '@commitlint/cli@19.8.1(@types/node@22.15.33)(typescript@5.8.3)': + '@commitlint/cli@19.8.1(@types/node@22.16.3)(typescript@5.8.3)': dependencies: '@commitlint/format': 19.8.1 '@commitlint/lint': 19.8.1 - '@commitlint/load': 19.8.1(@types/node@22.15.33)(typescript@5.8.3) + '@commitlint/load': 19.8.1(@types/node@22.16.3)(typescript@5.8.3) '@commitlint/read': 19.8.1 '@commitlint/types': 19.8.1 tinyexec: 1.0.1 @@ -9642,7 +9652,7 @@ snapshots: '@commitlint/rules': 19.8.1 '@commitlint/types': 19.8.1 - '@commitlint/load@19.8.1(@types/node@22.15.33)(typescript@5.8.3)': + '@commitlint/load@19.8.1(@types/node@22.16.3)(typescript@5.8.3)': dependencies: '@commitlint/config-validator': 19.8.1 '@commitlint/execute-rule': 19.8.1 @@ -9650,7 +9660,7 @@ snapshots: '@commitlint/types': 19.8.1 chalk: 5.4.1 cosmiconfig: 9.0.0(typescript@5.8.3) - cosmiconfig-typescript-loader: 6.1.0(@types/node@22.15.33)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3) + cosmiconfig-typescript-loader: 6.1.0(@types/node@22.16.3)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -10095,23 +10105,23 @@ snapshots: '@material/material-color-utilities@0.3.0': {} - '@microsoft/api-extractor-model@7.30.3(@types/node@22.15.33)': + '@microsoft/api-extractor-model@7.30.3(@types/node@22.16.3)': dependencies: '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.11.0(@types/node@22.15.33) + '@rushstack/node-core-library': 5.11.0(@types/node@22.16.3) transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.51.0(@types/node@22.15.33)': + '@microsoft/api-extractor@7.51.0(@types/node@22.16.3)': dependencies: - '@microsoft/api-extractor-model': 7.30.3(@types/node@22.15.33) + '@microsoft/api-extractor-model': 7.30.3(@types/node@22.16.3) '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.11.0(@types/node@22.15.33) + '@rushstack/node-core-library': 5.11.0(@types/node@22.16.3) '@rushstack/rig-package': 0.5.3 - '@rushstack/terminal': 0.15.0(@types/node@22.15.33) - '@rushstack/ts-command-line': 4.23.5(@types/node@22.15.33) + '@rushstack/terminal': 0.15.0(@types/node@22.16.3) + '@rushstack/ts-command-line': 4.23.5(@types/node@22.16.3) lodash: 4.17.21 minimatch: 3.0.8 resolve: 1.22.8 @@ -10842,7 +10852,7 @@ snapshots: '@rtsao/scc@1.1.0': {} - '@rushstack/node-core-library@5.11.0(@types/node@22.15.33)': + '@rushstack/node-core-library@5.11.0(@types/node@22.16.3)': dependencies: ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) @@ -10853,23 +10863,23 @@ snapshots: resolve: 1.22.8 semver: 7.5.4 optionalDependencies: - '@types/node': 22.15.33 + '@types/node': 22.16.3 '@rushstack/rig-package@0.5.3': dependencies: resolve: 1.22.8 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.15.0(@types/node@22.15.33)': + '@rushstack/terminal@0.15.0(@types/node@22.16.3)': dependencies: - '@rushstack/node-core-library': 5.11.0(@types/node@22.15.33) + '@rushstack/node-core-library': 5.11.0(@types/node@22.16.3) supports-color: 8.1.1 optionalDependencies: - '@types/node': 22.15.33 + '@types/node': 22.16.3 - '@rushstack/ts-command-line@4.23.5(@types/node@22.15.33)': + '@rushstack/ts-command-line@4.23.5(@types/node@22.16.3)': dependencies: - '@rushstack/terminal': 0.15.0(@types/node@22.15.33) + '@rushstack/terminal': 0.15.0(@types/node@22.16.3) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.2 @@ -11136,10 +11146,10 @@ snapshots: '@tanstack/query-core': 5.82.0 react: 19.1.0 - '@tanstack/react-router-devtools@1.125.6(@tanstack/react-router@1.125.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.125.4)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.5)(tiny-invariant@1.3.3)': + '@tanstack/react-router-devtools@1.127.0(@tanstack/react-router@1.127.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.127.0)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.5)(tiny-invariant@1.3.3)': dependencies: - '@tanstack/react-router': 1.125.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@tanstack/router-devtools-core': 1.125.4(@tanstack/router-core@1.125.4)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3) + '@tanstack/react-router': 1.127.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/router-devtools-core': 1.127.0(@tanstack/router-core@1.127.0)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) transitivePeerDependencies: @@ -11148,11 +11158,11 @@ snapshots: - solid-js - tiny-invariant - '@tanstack/react-router@1.125.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@tanstack/react-router@1.127.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/history': 1.121.34 '@tanstack/react-store': 0.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@tanstack/router-core': 1.125.4 + '@tanstack/router-core': 1.127.0 isbot: 5.1.28 jsesc: 3.1.0 react: 19.1.0 @@ -11179,18 +11189,20 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - '@tanstack/router-core@1.125.4': + '@tanstack/router-core@1.127.0': dependencies: '@tanstack/history': 1.121.34 '@tanstack/store': 0.7.0 cookie-es: 1.2.2 jsesc: 3.1.0 + seroval: 1.3.2 + seroval-plugins: 1.3.2(seroval@1.3.2) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/router-devtools-core@1.125.4(@tanstack/router-core@1.125.4)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)': + '@tanstack/router-devtools-core@1.127.0(@tanstack/router-core@1.127.0)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)': dependencies: - '@tanstack/router-core': 1.125.4 + '@tanstack/router-core': 1.127.0 clsx: 2.1.1 goober: 2.1.16(csstype@3.1.3) solid-js: 1.9.5 @@ -11198,9 +11210,9 @@ snapshots: optionalDependencies: csstype: 3.1.3 - '@tanstack/router-generator@1.125.4': + '@tanstack/router-generator@1.127.0': dependencies: - '@tanstack/router-core': 1.125.4 + '@tanstack/router-core': 1.127.0 '@tanstack/router-utils': 1.121.21 '@tanstack/virtual-file-routes': 1.121.21 prettier: 3.6.2 @@ -11211,7 +11223,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.125.6(@tanstack/react-router@1.125.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0))': + '@tanstack/router-plugin@1.127.0(@tanstack/react-router@1.127.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@babel/core': 7.27.7 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.7) @@ -11219,8 +11231,8 @@ snapshots: '@babel/template': 7.27.2 '@babel/traverse': 7.27.7 '@babel/types': 7.27.7 - '@tanstack/router-core': 1.125.4 - '@tanstack/router-generator': 1.125.4 + '@tanstack/router-core': 1.127.0 + '@tanstack/router-generator': 1.127.0 '@tanstack/router-utils': 1.121.21 '@tanstack/virtual-file-routes': 1.121.21 babel-dead-code-elimination: 1.0.10 @@ -11228,8 +11240,8 @@ snapshots: unplugin: 2.3.5 zod: 3.25.76 optionalDependencies: - '@tanstack/react-router': 1.125.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - vite: 7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) + '@tanstack/react-router': 1.127.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + vite: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - supports-color @@ -11244,9 +11256,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.125.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(zod@3.25.76)': + '@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.127.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(zod@3.25.76)': dependencies: - '@tanstack/react-router': 1.125.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/react-router': 1.127.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) zod: 3.25.76 '@tanstack/store@0.7.0': {} @@ -11364,7 +11376,7 @@ snapshots: '@types/adm-zip@0.5.7': dependencies: - '@types/node': 22.15.33 + '@types/node': 22.16.3 '@types/argparse@1.0.38': {} @@ -11395,12 +11407,12 @@ snapshots: dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 22.15.33 + '@types/node': 22.16.3 '@types/responselike': 1.0.3 '@types/conventional-commits-parser@5.0.0': dependencies: - '@types/node': 22.15.33 + '@types/node': 22.16.3 '@types/d3-array@3.2.1': {} @@ -11536,7 +11548,7 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 22.15.33 + '@types/node': 22.16.3 '@types/geojson@7946.0.14': {} @@ -11554,11 +11566,11 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 22.15.33 + '@types/node': 22.16.3 '@types/keyv@3.1.4': dependencies: - '@types/node': 22.15.33 + '@types/node': 22.16.3 '@types/lodash-es@4.17.12': dependencies: @@ -11574,7 +11586,7 @@ snapshots: '@types/node@16.18.108': {} - '@types/node@22.15.33': + '@types/node@22.16.3': dependencies: undici-types: 6.21.0 @@ -11606,7 +11618,7 @@ snapshots: '@types/responselike@1.0.3': dependencies: - '@types/node': 22.15.33 + '@types/node': 22.16.3 '@types/retry@0.12.2': {} @@ -11626,7 +11638,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.15.33 + '@types/node': 22.16.3 optional: true '@typescript-eslint/eslint-plugin@8.36.0(@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': @@ -11834,7 +11846,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.10.1': optional: true - '@vitejs/plugin-legacy@7.0.0(terser@5.36.0)(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0))': + '@vitejs/plugin-legacy@7.0.0(terser@5.36.0)(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@babel/core': 7.27.4 '@babel/preset-env': 7.27.2(@babel/core@7.27.4) @@ -11845,19 +11857,19 @@ snapshots: regenerator-runtime: 0.14.1 systemjs: 6.15.1 terser: 5.36.0 - vite: 7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react-swc@3.10.2(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0))': + '@vitejs/plugin-react-swc@3.10.2(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.11 '@swc/core': 1.12.1 - vite: 7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - '@swc/helpers' - '@vitejs/plugin-react@4.6.0(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0))': + '@vitejs/plugin-react@4.6.0(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@babel/core': 7.27.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.4) @@ -11865,7 +11877,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.19 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - supports-color @@ -12512,7 +12524,7 @@ snapshots: dependencies: compare-func: 2.0.0 - conventional-changelog-conventionalcommits@9.0.0: + conventional-changelog-conventionalcommits@9.1.0: dependencies: compare-func: 2.0.0 @@ -12543,9 +12555,9 @@ snapshots: core-js@3.44.0: {} - cosmiconfig-typescript-loader@6.1.0(@types/node@22.15.33)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3): + cosmiconfig-typescript-loader@6.1.0(@types/node@22.16.3)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3): dependencies: - '@types/node': 22.15.33 + '@types/node': 22.16.3 cosmiconfig: 9.0.0(typescript@5.8.3) jiti: 2.4.2 typescript: 5.8.3 @@ -14633,10 +14645,10 @@ snapshots: kind-of@6.0.3: {} - knip@5.61.3(@types/node@22.15.33)(typescript@5.8.3): + knip@5.61.3(@types/node@22.16.3)(typescript@5.8.3): dependencies: '@nodelib/fs.walk': 1.2.8 - '@types/node': 22.15.33 + '@types/node': 22.16.3 fast-glob: 3.3.3 formatly: 0.2.4 jiti: 2.4.2 @@ -16259,8 +16271,14 @@ snapshots: dependencies: seroval: 1.2.1 + seroval-plugins@1.3.2(seroval@1.3.2): + dependencies: + seroval: 1.3.2 + seroval@1.2.1: {} + seroval@1.3.2: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -17202,9 +17220,9 @@ snapshots: - rollup - supports-color - vite-plugin-dts@4.5.4(@types/node@22.15.33)(rollup@4.40.0)(typescript@5.8.3)(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)): + vite-plugin-dts@4.5.4(@types/node@22.16.3)(rollup@4.40.0)(typescript@5.8.3)(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)): dependencies: - '@microsoft/api-extractor': 7.51.0(@types/node@22.15.33) + '@microsoft/api-extractor': 7.51.0(@types/node@22.16.3) '@rollup/pluginutils': 5.1.4(rollup@4.40.0) '@volar/typescript': 2.4.11 '@vue/language-core': 2.2.0(typescript@5.8.3) @@ -17215,13 +17233,13 @@ snapshots: magic-string: 0.30.17 typescript: 5.8.3 optionalDependencies: - vite: 7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-html@3.2.2(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)): + vite-plugin-html@3.2.2(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)): dependencies: '@rollup/pluginutils': 4.2.1 colorette: 2.0.20 @@ -17235,39 +17253,39 @@ snapshots: html-minifier-terser: 6.1.0 node-html-parser: 5.4.2 pathe: 0.2.0 - vite: 7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) - vite-plugin-sass-dts@1.3.31(postcss@8.5.6)(prettier@3.6.2)(sass-embedded@1.89.2)(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)): + vite-plugin-sass-dts@1.3.31(postcss@8.5.6)(prettier@3.6.2)(sass-embedded@1.89.2)(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)): dependencies: postcss: 8.5.6 postcss-js: 4.0.1(postcss@8.5.6) prettier: 3.6.2 sass-embedded: 1.89.2 - vite: 7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) - vite-plugin-svgr@4.3.0(rollup@4.40.0)(typescript@5.8.3)(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)): + vite-plugin-svgr@4.3.0(rollup@4.40.0)(typescript@5.8.3)(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)): dependencies: '@rollup/pluginutils': 5.1.3(rollup@4.40.0) '@svgr/core': 8.1.0(typescript@5.8.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3)) - vite: 7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - rollup - supports-color - typescript - vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)): + vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0)): dependencies: debug: 4.3.7 globrex: 0.1.2 tsconfck: 3.0.3(typescript@5.8.3) optionalDependencies: - vite: 7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - supports-color - typescript - vite@7.0.3(@types/node@22.15.33)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0): + vite@7.0.4(@types/node@22.16.3)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.89.2)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: esbuild: 0.25.0 fdir: 6.4.6(picomatch@4.0.2) @@ -17276,7 +17294,7 @@ snapshots: rollup: 4.40.0 tinyglobby: 0.2.14 optionalDependencies: - '@types/node': 22.15.33 + '@types/node': 22.16.3 fsevents: 2.3.3 jiti: 2.4.2 less: 4.2.0 diff --git a/clash-verge-rev/package.json b/clash-verge-rev/package.json index b28affdf51..9e415e1079 100644 --- a/clash-verge-rev/package.json +++ b/clash-verge-rev/package.json @@ -34,7 +34,7 @@ "@mui/icons-material": "^7.2.0", "@mui/lab": "7.0.0-beta.14", "@mui/material": "^7.2.0", - "@mui/x-data-grid": "^8.7.0", + "@mui/x-data-grid": "^8.8.0", "@tauri-apps/api": "2.6.0", "@tauri-apps/plugin-clipboard-manager": "^2.3.0", "@tauri-apps/plugin-dialog": "^2.3.0", @@ -53,7 +53,7 @@ "dayjs": "1.11.13", "foxact": "^0.2.49", "glob": "^11.0.3", - "i18next": "^25.3.1", + "i18next": "^25.3.2", "js-yaml": "^4.1.0", "json-schema": "^0.4.0", "lodash-es": "^4.17.21", @@ -96,7 +96,7 @@ "sass": "^1.89.2", "terser": "^5.43.1", "typescript": "^5.8.3", - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-monaco-editor": "^1.1.0", "vite-plugin-svgr": "^4.3.0" }, diff --git a/clash-verge-rev/pnpm-lock.yaml b/clash-verge-rev/pnpm-lock.yaml index e63ab01160..798365aa79 100644 --- a/clash-verge-rev/pnpm-lock.yaml +++ b/clash-verge-rev/pnpm-lock.yaml @@ -36,8 +36,8 @@ importers: specifier: ^7.2.0 version: 7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@mui/x-data-grid': - specifier: ^8.7.0 - version: 8.7.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^8.8.0 + version: 8.8.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tauri-apps/api': specifier: 2.6.0 version: 2.6.0 @@ -93,8 +93,8 @@ importers: specifier: ^11.0.3 version: 11.0.3 i18next: - specifier: ^25.3.1 - version: 25.3.1(typescript@5.8.3) + specifier: ^25.3.2 + version: 25.3.2(typescript@5.8.3) js-yaml: specifier: ^4.1.0 version: 4.1.0 @@ -130,7 +130,7 @@ importers: version: 7.60.0(react@19.1.0) react-i18next: specifier: 15.6.0 - version: 15.6.0(i18next@25.3.1(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) + version: 15.6.0(i18next@25.3.2(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) react-markdown: specifier: 10.1.0 version: 10.1.0(@types/react@19.1.8)(react@19.1.0) @@ -179,10 +179,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-legacy': specifier: ^7.0.0 - version: 7.0.0(terser@5.43.1)(vite@7.0.2(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)) + version: 7.0.0(terser@5.43.1)(vite@7.0.4(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)) '@vitejs/plugin-react': specifier: 4.6.0 - version: 4.6.0(vite@7.0.2(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)) + version: 4.6.0(vite@7.0.4(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)) adm-zip: specifier: ^0.5.16 version: 0.5.16 @@ -217,14 +217,14 @@ importers: specifier: ^5.8.3 version: 5.8.3 vite: - specifier: ^7.0.2 - version: 7.0.2(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) + specifier: ^7.0.4 + version: 7.0.4(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) vite-plugin-monaco-editor: specifier: ^1.1.0 version: 1.1.0(monaco-editor@0.52.2) vite-plugin-svgr: specifier: ^4.3.0 - version: 4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@7.0.2(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)) + version: 4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@7.0.4(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)) packages: @@ -1120,8 +1120,8 @@ packages: '@types/react': optional: true - '@mui/x-data-grid@8.7.0': - resolution: {integrity: sha512-3hVjnADSBXEd/7f+CHlxTNhqJrnRL0XkTbvI+yfttPuqLHYQJwNUR7p7d/VtyRcR6QlaK19+Bu8Q6u0Ygmw/PQ==} + '@mui/x-data-grid@8.8.0': + resolution: {integrity: sha512-xWoBmxHi5JvT0QvAYGYJYNy4DEi+Lez+lrsqw3YV7z0jEYyJoV9vjFCiFE4QmG6IPg62B1gZHYE5AkDLFCvPkw==} engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.9.0 @@ -1136,8 +1136,8 @@ packages: '@emotion/styled': optional: true - '@mui/x-internals@8.7.0': - resolution: {integrity: sha512-1aduds7L2i6t0HIFNlqG4UB07SVEg+wcnJ9GGu8B/X8EVwO72Rt+rc8ZlqK10ooscq1AlTwi2dd0q+hz+aWk+Q==} + '@mui/x-internals@8.8.0': + resolution: {integrity: sha512-qTRK5oINkAjZ7sIHpSnESLNq1xtQUmmfmGscYUSEP0uHoYh6pKkNWH9+7yzggRHuTv+4011VBwN9s+efrk+xZg==} engines: {node: '>=14.0.0'} peerDependencies: '@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0 @@ -2093,8 +2093,8 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} - i18next@25.3.1: - resolution: {integrity: sha512-S4CPAx8LfMOnURnnJa8jFWvur+UX/LWcl6+61p9VV7SK2m0445JeBJ6tLD0D5SR0H29G4PYfWkEhivKG5p4RDg==} + i18next@25.3.2: + resolution: {integrity: sha512-JSnbZDxRVbphc5jiptxr3o2zocy5dEqpVm9qCGdJwRNO+9saUJS0/u4LnM/13C23fUEWxAylPqKU/NpMV/IjqA==} peerDependencies: typescript: ^5 peerDependenciesMeta: @@ -2861,8 +2861,8 @@ packages: peerDependencies: vite: '>=2.6.0' - vite@7.0.2: - resolution: {integrity: sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==} + vite@7.0.4: + resolution: {integrity: sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3980,13 +3980,13 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 - '@mui/x-data-grid@8.7.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@mui/x-data-grid@8.8.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.27.6 '@mui/material': 7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@mui/system': 7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0) '@mui/utils': 7.2.0(@types/react@19.1.8)(react@19.1.0) - '@mui/x-internals': 8.7.0(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0) + '@mui/x-internals': 8.8.0(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0) clsx: 2.1.1 prop-types: 15.8.1 react: 19.1.0 @@ -3998,7 +3998,7 @@ snapshots: transitivePeerDependencies: - '@types/react' - '@mui/x-internals@8.7.0(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)': + '@mui/x-internals@8.8.0(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)': dependencies: '@babel/runtime': 7.27.6 '@mui/system': 7.2.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0) @@ -4427,7 +4427,7 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-legacy@7.0.0(terser@5.43.1)(vite@7.0.2(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1))': + '@vitejs/plugin-legacy@7.0.0(terser@5.43.1)(vite@7.0.4(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1))': dependencies: '@babel/core': 7.27.4 '@babel/preset-env': 7.27.2(@babel/core@7.27.4) @@ -4438,11 +4438,11 @@ snapshots: regenerator-runtime: 0.14.1 systemjs: 6.15.1 terser: 5.43.1 - vite: 7.0.2(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) + vite: 7.0.4(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.6.0(vite@7.0.2(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1))': + '@vitejs/plugin-react@4.6.0(vite@7.0.4(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1))': dependencies: '@babel/core': 7.27.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.4) @@ -4450,7 +4450,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.19 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.0.2(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) + vite: 7.0.4(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) transitivePeerDependencies: - supports-color @@ -4935,7 +4935,7 @@ snapshots: transitivePeerDependencies: - supports-color - i18next@25.3.1(typescript@5.8.3): + i18next@25.3.2(typescript@5.8.3): dependencies: '@babel/runtime': 7.27.6 optionalDependencies: @@ -5459,11 +5459,11 @@ snapshots: dependencies: react: 19.1.0 - react-i18next@15.6.0(i18next@25.3.1(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3): + react-i18next@15.6.0(i18next@25.3.2(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3): dependencies: '@babel/runtime': 7.27.6 html-parse-stringify: 3.0.1 - i18next: 25.3.1(typescript@5.8.3) + i18next: 25.3.2(typescript@5.8.3) react: 19.1.0 optionalDependencies: react-dom: 19.1.0(react@19.1.0) @@ -5821,18 +5821,18 @@ snapshots: dependencies: monaco-editor: 0.52.2 - vite-plugin-svgr@4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@7.0.2(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)): + vite-plugin-svgr@4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@7.0.4(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)): dependencies: '@rollup/pluginutils': 5.1.4(rollup@4.40.2) '@svgr/core': 8.1.0(typescript@5.8.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3)) - vite: 7.0.2(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) + vite: 7.0.4(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) transitivePeerDependencies: - rollup - supports-color - typescript - vite@7.0.2(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1): + vite@7.0.4(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1): dependencies: esbuild: 0.25.4 fdir: 6.4.6(picomatch@4.0.2) diff --git a/clash-verge-rev/src-tauri/Cargo.lock b/clash-verge-rev/src-tauri/Cargo.lock index 39bfa0e568..b4bce4dee3 100644 --- a/clash-verge-rev/src-tauri/Cargo.lock +++ b/clash-verge-rev/src-tauri/Cargo.lock @@ -887,21 +887,11 @@ dependencies = [ [[package]] name = "bzip2" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff" dependencies = [ - "bzip2-sys", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.13+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" -dependencies = [ - "cc", - "pkg-config", + "libbz2-rs-sys", ] [[package]] @@ -3759,6 +3749,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "libbz2-rs-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775bf80d5878ab7c2b1080b5351a48b2f737d9f6f8b383574eebcc22be0dfccb" + [[package]] name = "libc" version = "0.2.174" @@ -5322,6 +5318,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppmd-rust" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c834641d8ad1b348c9ee86dec3b9840d805acd5f24daa5f90c788951a52ff59b" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -6848,9 +6850,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.35.2" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" +checksum = "aab138f5c1bb35231de19049060a87977ad23e04f2303e953bc5c2947ac7dec4" dependencies = [ "libc", "memchr", @@ -9705,9 +9707,9 @@ dependencies = [ [[package]] name = "zip" -version = "4.2.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ab361742de920c5535880f89bbd611ee62002bf11341d16a5f057bb8ba6899" +checksum = "9aed4ac33e8eb078c89e6cbb1d5c4c7703ec6d299fc3e7c3695af8f8b423468b" dependencies = [ "aes", "arbitrary", @@ -9722,6 +9724,7 @@ dependencies = [ "liblzma", "memchr", "pbkdf2", + "ppmd-rust", "sha1", "time", "zeroize", diff --git a/clash-verge-rev/src-tauri/Cargo.toml b/clash-verge-rev/src-tauri/Cargo.toml index cfca33fcec..de3fa31764 100755 --- a/clash-verge-rev/src-tauri/Cargo.toml +++ b/clash-verge-rev/src-tauri/Cargo.toml @@ -25,7 +25,7 @@ dunce = "1.0.5" log4rs = "1.3.0" nanoid = "0.4" chrono = "0.4.41" -sysinfo = "0.35.2" +sysinfo = "0.36.0" boa_engine = "0.20.0" serde_json = "1.0.140" serde_yaml = "0.9.34" @@ -62,7 +62,7 @@ tauri-plugin-clipboard-manager = "2.3.0" tauri-plugin-deep-link = "2.4.0" tauri-plugin-devtools = "2.0.0" tauri-plugin-window-state = "2.3.0" -zip = "4.2.0" +zip = "4.3.0" reqwest_dav = "0.2.1" aes-gcm = { version = "0.10.3", features = ["std"] } base64 = "0.22.1" diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh index 7a435ac2e7..2f14ea2b5c 100755 --- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh +++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh @@ -2080,7 +2080,7 @@ acl_app() { } start() { - mkdir -p /tmp/etc $TMP_PATH $TMP_BIN_PATH $TMP_SCRIPT_FUNC_PATH $TMP_ROUTE_PATH $TMP_ACL_PATH $TMP_PATH2 + mkdir -p /tmp/etc /tmp/log $TMP_PATH $TMP_BIN_PATH $TMP_SCRIPT_FUNC_PATH $TMP_ROUTE_PATH $TMP_ACL_PATH $TMP_PATH2 get_config export V2RAY_LOCATION_ASSET=$(config_t_get global_rules v2ray_location_asset "/usr/share/v2ray/") export XRAY_LOCATION_ASSET=$V2RAY_LOCATION_ASSET diff --git a/openwrt-passwall2/luci-app-passwall2/Makefile b/openwrt-passwall2/luci-app-passwall2/Makefile index b1e2039f7e..525ddf5dac 100644 --- a/openwrt-passwall2/luci-app-passwall2/Makefile +++ b/openwrt-passwall2/luci-app-passwall2/Makefile @@ -5,7 +5,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall2 -PKG_VERSION:=25.6.21 +PKG_VERSION:=25.7.11 PKG_RELEASE:=1 PKG_CONFIG_DEPENDS:= \ diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/controller/passwall2.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/controller/passwall2.lua index e621626729..d3ec9b29b1 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/controller/passwall2.lua +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/controller/passwall2.lua @@ -8,6 +8,7 @@ local http = require "luci.http" local util = require "luci.util" local i18n = require "luci.i18n" local fs = api.fs +local jsonStringify = luci.jsonc.stringify function index() if not nixio.fs.access("/etc/config/passwall2") then @@ -40,13 +41,14 @@ function index() end entry({"admin", "services", appname, "app_update"}, cbi(appname .. "/client/app_update"), _("App Update"), 95).leaf = true entry({"admin", "services", appname, "rule"}, cbi(appname .. "/client/rule"), _("Rule Manage"), 96).leaf = true + entry({"admin", "services", appname, "geoview"}, form(appname .. "/client/geoview"), _("Geo View"), 97).leaf = true entry({"admin", "services", appname, "node_subscribe_config"}, cbi(appname .. "/client/node_subscribe_config")).leaf = true entry({"admin", "services", appname, "node_config"}, cbi(appname .. "/client/node_config")).leaf = true entry({"admin", "services", appname, "shunt_rules"}, cbi(appname .. "/client/shunt_rules")).leaf = true entry({"admin", "services", appname, "socks_config"}, cbi(appname .. "/client/socks_config")).leaf = true entry({"admin", "services", appname, "acl"}, cbi(appname .. "/client/acl"), _("Access control"), 98).leaf = true entry({"admin", "services", appname, "acl_config"}, cbi(appname .. "/client/acl_config")).leaf = true - entry({"admin", "services", appname, "log"}, form(appname .. "/client/log"), _("Log Maint"), 999).leaf = true + entry({"admin", "services", appname, "log"}, form(appname .. "/client/log"), _("Watch Logs"), 999).leaf = true --[[ Server ]] entry({"admin", "services", appname, "server"}, cbi(appname .. "/server/index"), _("Server-Side"), 99).leaf = true @@ -90,40 +92,44 @@ function index() end --[[Backup]] - entry({"admin", "services", appname, "backup"}, call("create_backup")).leaf = true + entry({"admin", "services", appname, "create_backup"}, call("create_backup")).leaf = true + entry({"admin", "services", appname, "restore_backup"}, call("restore_backup")).leaf = true + + --[[geoview]] + entry({"admin", "services", appname, "geo_view"}, call("geo_view")).leaf = true end local function http_write_json(content) http.prepare_content("application/json") - http.write_json(content or {code = 1}) + http.write(jsonStringify(content or {code = 1})) end function reset_config() luci.sys.call('/etc/init.d/passwall2 stop') luci.sys.call('[ -f "/usr/share/passwall2/0_default_config" ] && cp -f /usr/share/passwall2/0_default_config /etc/config/passwall2') - luci.http.redirect(api.url()) + http.redirect(api.url()) end function show_menu() api.sh_uci_del(appname, "@global[0]", "hide_from_luci", true) luci.sys.call("rm -rf /tmp/luci-*") luci.sys.call("/etc/init.d/rpcd restart >/dev/null") - luci.http.redirect(api.url()) + http.redirect(api.url()) end function hide_menu() api.sh_uci_set(appname, "@global[0]", "hide_from_luci", "1", true) luci.sys.call("rm -rf /tmp/luci-*") luci.sys.call("/etc/init.d/rpcd restart >/dev/null") - luci.http.redirect(luci.dispatcher.build_url("admin", "status", "overview")) + http.redirect(luci.dispatcher.build_url("admin", "status", "overview")) end function link_add_node() -- 分片接收以突破uhttpd的限制 local tmp_file = "/tmp/links.conf" - local chunk = luci.http.formvalue("chunk") - local chunk_index = tonumber(luci.http.formvalue("chunk_index")) - local total_chunks = tonumber(luci.http.formvalue("total_chunks")) + local chunk = http.formvalue("chunk") + local chunk_index = tonumber(http.formvalue("chunk_index")) + local total_chunks = tonumber(http.formvalue("total_chunks")) if chunk and chunk_index ~= nil and total_chunks ~= nil then -- 按顺序拼接到文件 @@ -144,8 +150,8 @@ function link_add_node() end function socks_autoswitch_add_node() - local id = luci.http.formvalue("id") - local key = luci.http.formvalue("key") + local id = http.formvalue("id") + local key = http.formvalue("key") if id and id ~= "" and key and key ~= "" then uci:set(appname, id, "enable_autoswitch", "1") local new_list = uci:get(appname, id, "autoswitch_backup_node") or {} @@ -162,12 +168,12 @@ function socks_autoswitch_add_node() uci:set_list(appname, id, "autoswitch_backup_node", new_list) api.uci_save(uci, appname) end - luci.http.redirect(api.url("socks_config", id)) + http.redirect(api.url("socks_config", id)) end function socks_autoswitch_remove_node() - local id = luci.http.formvalue("id") - local key = luci.http.formvalue("key") + local id = http.formvalue("id") + local key = http.formvalue("key") if id and id ~= "" and key and key ~= "" then uci:set(appname, id, "enable_autoswitch", "1") local new_list = uci:get(appname, id, "autoswitch_backup_node") or {} @@ -179,19 +185,19 @@ function socks_autoswitch_remove_node() uci:set_list(appname, id, "autoswitch_backup_node", new_list) api.uci_save(uci, appname) end - luci.http.redirect(api.url("socks_config", id)) + http.redirect(api.url("socks_config", id)) end function gen_client_config() - local id = luci.http.formvalue("id") + local id = http.formvalue("id") local config_file = api.TMP_PATH .. "/config_" .. id luci.sys.call(string.format("/usr/share/passwall2/app.sh run_socks flag=config_%s node=%s bind=127.0.0.1 socks_port=1080 config_file=%s no_run=1", id, id, config_file)) if nixio.fs.access(config_file) then - luci.http.prepare_content("application/json") - luci.http.write(luci.sys.exec("cat " .. config_file)) + http.prepare_content("application/json") + http.write(luci.sys.exec("cat " .. config_file)) luci.sys.call("rm -f " .. config_file) else - luci.http.redirect(api.url("node_list")) + http.redirect(api.url("node_list")) end end @@ -201,38 +207,37 @@ function get_now_use_node() if node then e["global"] = node end - luci.http.prepare_content("application/json") - luci.http.write_json(e) + http_write_json(e) end function get_redir_log() - local id = luci.http.formvalue("id") - local name = luci.http.formvalue("name") + local id = http.formvalue("id") + local name = http.formvalue("name") local file_path = "/tmp/etc/passwall2/acl/" .. id .. "/" .. name .. ".log" if nixio.fs.access(file_path) then local content = luci.sys.exec("tail -n 19999 '" .. file_path .. "'") content = content:gsub("\n", "
") - luci.http.write(content) + http.write(content) else - luci.http.write(string.format("", i18n.translate("Not enabled log"))) + http.write(string.format("", i18n.translate("Not enabled log"))) end end function get_socks_log() - local name = luci.http.formvalue("name") + local name = http.formvalue("name") local path = "/tmp/etc/passwall2/SOCKS_" .. name .. ".log" if nixio.fs.access(path) then local content = luci.sys.exec("tail -n 5000 ".. path) content = content:gsub("\n", "
") - luci.http.write(content) + http.write(content) else - luci.http.write(string.format("", i18n.translate("Not enabled log"))) + http.write(string.format("", i18n.translate("Not enabled log"))) end end function get_log() -- luci.sys.exec("[ -f /tmp/log/passwall2.log ] && sed '1!G;h;$!d' /tmp/log/passwall2.log > /tmp/log/passwall2_show.log") - luci.http.write(luci.sys.exec("[ -f '/tmp/log/passwall2.log' ] && cat /tmp/log/passwall2.log")) + http.write(luci.sys.exec("[ -f '/tmp/log/passwall2.log' ] && cat /tmp/log/passwall2.log")) end function clear_log() @@ -242,20 +247,18 @@ end function index_status() local e = {} e["global_status"] = luci.sys.call("/bin/busybox top -bn1 | grep -v 'grep' | grep '/tmp/etc/passwall2/bin/' | grep 'default' | grep 'global' >/dev/null") == 0 - luci.http.prepare_content("application/json") - luci.http.write_json(e) + http_write_json(e) end function haproxy_status() local e = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v grep | grep '%s/bin/' | grep haproxy >/dev/null", appname)) == 0 - luci.http.prepare_content("application/json") - luci.http.write_json(e) + http_write_json(e) end function socks_status() local e = {} - local index = luci.http.formvalue("index") - local id = luci.http.formvalue("id") + local index = http.formvalue("index") + local id = http.formvalue("id") e.index = index e.socks_status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '%s' | grep 'SOCKS_' > /dev/null", appname, id)) == 0 local use_http = uci:get(appname, id, "http_port") or 0 @@ -264,14 +267,13 @@ function socks_status() e.use_http = 1 e.http_status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '%s' | grep -E 'HTTP_|HTTP2SOCKS' > /dev/null", appname, id)) == 0 end - luci.http.prepare_content("application/json") - luci.http.write_json(e) + http_write_json(e) end function connect_status() local e = {} e.use_time = "" - local url = luci.http.formvalue("url") + local url = http.formvalue("url") local result = luci.sys.exec('curl --connect-timeout 3 -o /dev/null -I -sk -w "%{http_code}:%{time_appconnect}" ' .. url) local code = tonumber(luci.sys.exec("echo -n '" .. result .. "' | awk -F ':' '{print $1}'") or "0") if code ~= 0 then @@ -283,15 +285,14 @@ function connect_status() end e.ping_type = "curl" end - luci.http.prepare_content("application/json") - luci.http.write_json(e) + http_write_json(e) end function ping_node() - local index = luci.http.formvalue("index") - local address = luci.http.formvalue("address") - local port = luci.http.formvalue("port") - local type = luci.http.formvalue("type") or "icmp" + local index = http.formvalue("index") + local address = http.formvalue("address") + local port = http.formvalue("port") + local type = http.formvalue("type") or "icmp" local e = {} e.index = index if type == "tcping" and luci.sys.exec("echo -n $(command -v tcping)") ~= "" then @@ -302,13 +303,12 @@ function ping_node() else e.ping = luci.sys.exec("echo -n $(ping -c 1 -W 1 %q 2>&1 | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null" % address) end - luci.http.prepare_content("application/json") - luci.http.write_json(e) + http_write_json(e) end function urltest_node() - local index = luci.http.formvalue("index") - local id = luci.http.formvalue("id") + local index = http.formvalue("index") + local id = http.formvalue("id") local e = {} e.index = index local result = luci.sys.exec(string.format("/usr/share/passwall2/test.sh url_test_node %s %s", id, "urltest_node")) @@ -321,21 +321,20 @@ function urltest_node() e.use_time = string.format("%.2f", use_time / 1000) end end - luci.http.prepare_content("application/json") - luci.http.write_json(e) + http_write_json(e) end function set_node() - local type = luci.http.formvalue("type") - local config = luci.http.formvalue("config") - local section = luci.http.formvalue("section") + local type = http.formvalue("type") + local config = http.formvalue("config") + local section = http.formvalue("section") uci:set(appname, type, config, section) api.uci_save(uci, appname, true, true) - luci.http.redirect(api.url("log")) + http.redirect(api.url("log")) end function copy_node() - local section = luci.http.formvalue("section") + local section = http.formvalue("section") local uuid = api.gen_short_uuid() uci:section(appname, "nodes", uuid) for k, v in pairs(uci:get_all(appname, section)) do @@ -352,11 +351,13 @@ function copy_node() uci:delete(appname, uuid, "add_from") uci:set(appname, uuid, "add_mode", 1) api.uci_save(uci, appname) - luci.http.redirect(api.url("node_config", uuid)) + http.redirect(api.url("node_config", uuid)) end function clear_all_nodes() uci:set(appname, '@global[0]', "enabled", "0") + uci:set(appname, '@global[0]', "socks_enabled", "0") + uci:set(appname, '@haproxy_config[0]', "balancing_enable", "0") uci:delete(appname, '@global[0]', "node") uci:foreach(appname, "socks", function(t) uci:delete(appname, t[".name"]) @@ -371,12 +372,15 @@ function clear_all_nodes() uci:foreach(appname, "nodes", function(node) uci:delete(appname, node['.name']) end) - api.uci_save(uci, appname, true) - luci.sys.call("/etc/init.d/" .. appname .. " stop") + uci:foreach(appname, "subscribe_list", function(t) + uci:delete(appname, t[".name"], "md5") + end) + + api.uci_save(uci, appname, true, true) end function delete_select_nodes() - local ids = luci.http.formvalue("ids") + local ids = http.formvalue("ids") string.gsub(ids, '[^' .. "," .. ']+', function(w) if (uci:get(appname, "@global[0]", "node") or "") == w then uci:delete(appname, '@global[0]', "node") @@ -413,38 +417,47 @@ function delete_select_nodes() uci:delete(appname, t[".name"], "chain_proxy") end end) + if (uci:get(appname, w, "add_mode") or "0") == "2" then + local add_from = uci:get(appname, w, "add_from") or "" + if add_from ~= "" then + uci:foreach(appname, "subscribe_list", function(t) + if t["remark"] == add_from then + uci:delete(appname, t[".name"], "md5") + end + end) + end + end uci:delete(appname, w) end) - api.uci_save(uci, appname, true) - luci.sys.call("/etc/init.d/" .. appname .. " restart > /dev/null 2>&1 &") + api.uci_save(uci, appname, true, true) end function update_rules() - local update = luci.http.formvalue("update") + local update = http.formvalue("update") luci.sys.call("lua /usr/share/passwall2/rule_update.lua log '" .. update .. "' > /dev/null 2>&1 &") http_write_json() end function server_user_status() local e = {} - e.index = luci.http.formvalue("index") - e.status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v 'grep' | grep '%s/bin/' | grep -i '%s' >/dev/null", appname .. "_server", luci.http.formvalue("id"))) == 0 + e.index = http.formvalue("index") + e.status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v 'grep' | grep '%s/bin/' | grep -i '%s' >/dev/null", appname .. "_server", http.formvalue("id"))) == 0 http_write_json(e) end function server_user_log() - local id = luci.http.formvalue("id") + local id = http.formvalue("id") if nixio.fs.access("/tmp/etc/passwall2_server/" .. id .. ".log") then local content = luci.sys.exec("cat /tmp/etc/passwall2_server/" .. id .. ".log") content = content:gsub("\n", "
") - luci.http.write(content) + http.write(content) else - luci.http.write(string.format("", i18n.translate("Not enabled log"))) + http.write(string.format("", i18n.translate("Not enabled log"))) end end function server_get_log() - luci.http.write(luci.sys.exec("[ -f '/tmp/log/passwall2_server.log' ] && cat /tmp/log/passwall2_server.log")) + http.write(luci.sys.exec("[ -f '/tmp/log/passwall2_server.log' ] && cat /tmp/log/passwall2_server.log")) end function server_clear_log() @@ -475,12 +488,13 @@ function com_update(comname) http_write_json(json) end +local backup_files = { + "/etc/config/passwall2", + "/etc/config/passwall2_server", + "/usr/share/passwall2/domains_excluded" +} + function create_backup() - local backup_files = { - "/etc/config/passwall2", - "/etc/config/passwall2_server", - "/usr/share/passwall2/domains_excluded" - } local date = os.date("%y%m%d%H%M") local tar_file = "/tmp/passwall2-" .. date .. "-backup.tar.gz" fs.remove(tar_file) @@ -493,15 +507,115 @@ function create_backup() fs.remove(tar_file) end +function restore_backup() + local ok, err = pcall(function() + local filename = http.formvalue("filename") + local chunk = http.formvalue("chunk") + local chunk_index = tonumber(http.formvalue("chunk_index") or "-1") + local total_chunks = tonumber(http.formvalue("total_chunks") or "-1") + if not filename or not chunk then + http_write_json({ status = "error", message = "Missing filename or chunk" }) + return + end + local file_path = "/tmp/" .. filename + local decoded = nixio.bin.b64decode(chunk) + local fp = io.open(file_path, "a+") + if not fp then + http_write_json({ status = "error", message = "Failed to open file for writing: " .. file_path }) + return + end + fp:write(decoded) + fp:close() + if chunk_index + 1 == total_chunks then + api.sys.call("echo '' > /tmp/log/passwall2.log") + api.log(" * PassWall2 配置文件上传成功…") + local temp_dir = '/tmp/passwall_bak' + api.sys.call("mkdir -p " .. temp_dir) + if api.sys.call("tar -xzf " .. file_path .. " -C " .. temp_dir) == 0 then + for _, backup_file in ipairs(backup_files) do + local temp_file = temp_dir .. backup_file + if fs.access(temp_file) then + api.sys.call("cp -f " .. temp_file .. " " .. backup_file) + end + end + api.log(" * PassWall2 配置还原成功…") + api.log(" * 重启 PassWall2 服务中…\n") + api.sys.call('/etc/init.d/passwall2 restart > /dev/null 2>&1 &') + api.sys.call('/etc/init.d/passwall2_server restart > /dev/null 2>&1 &') + else + api.log(" * PassWall2 配置文件解压失败,请重试!") + end + api.sys.call("rm -rf " .. temp_dir) + fs.remove(file_path) + http_write_json({ status = "success", message = "Upload completed", path = file_path }) + else + http_write_json({ status = "success", message = "Chunk received" }) + end + end) + if not ok then + http_write_json({ status = "error", message = tostring(err) }) + end +end + +function geo_view() + local action = luci.http.formvalue("action") + local value = luci.http.formvalue("value") + if not value or value == "" then + http.prepare_content("text/plain") + http.write(i18n.translate("Please enter query content!")) + return + end + local geo_dir = (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") + local geosite_path = geo_dir .. "/geosite.dat" + local geoip_path = geo_dir .. "/geoip.dat" + local geo_type, file_path, cmd + local geo_string = "" + if action == "lookup" then + if api.datatypes.ipaddr(value) or api.datatypes.ip6addr(value) then + geo_type, file_path = "geoip", geoip_path + else + geo_type, file_path = "geosite", geosite_path + end + cmd = string.format("geoview -type %s -action lookup -input '%s' -value '%s' -lowmem=true", geo_type, file_path, value) + geo_string = luci.sys.exec(cmd):lower() + if geo_string ~= "" then + local lines = {} + for line in geo_string:gmatch("([^\n]*)\n?") do + if line ~= "" then + table.insert(lines, geo_type .. ":" .. line) + end + end + geo_string = table.concat(lines, "\n") + end + elseif action == "extract" then + local prefix, list = value:match("^(geoip:)(.*)$") + if not prefix then + prefix, list = value:match("^(geosite:)(.*)$") + end + if prefix and list and list ~= "" then + geo_type = prefix:sub(1, -2) + file_path = (geo_type == "geoip") and geoip_path or geosite_path + cmd = string.format("geoview -type %s -action extract -input '%s' -list '%s' -lowmem=true", geo_type, file_path, list) + geo_string = luci.sys.exec(cmd) + end + end + http.prepare_content("text/plain") + if geo_string and geo_string ~="" then + http.write(geo_string) + else + http.write(i18n.translate("No results were found!")) + end +end + function subscribe_del_node() - local remark = luci.http.formvalue("remark") + local remark = http.formvalue("remark") if remark and remark ~= "" then luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate " .. luci.util.shellquote(remark) .. " > /dev/null 2>&1") end - luci.http.status(200, "OK") + http.status(200, "OK") end function subscribe_del_all() luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate > /dev/null 2>&1") - luci.http.status(200, "OK") + http.status(200, "OK") end diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/geoview.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/geoview.lua new file mode 100644 index 0000000000..071d8e80a2 --- /dev/null +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/geoview.lua @@ -0,0 +1,16 @@ +local api = require "luci.passwall2.api" +local appname = "passwall2" +local fs = api.fs +local uci = api.uci + +local geo_dir = (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") +local geosite_path = geo_dir .. "/geosite.dat" +local geoip_path = geo_dir .. "/geoip.dat" +if fs.access(geosite_path) and fs.access(geoip_path) then + f = SimpleForm(appname) + f.reset = false + f.submit = false + f:append(Template(appname .. "/rule/geoview")) +end + +return f \ No newline at end of file diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua index 3ffe45e19b..4d69d1d1ba 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua @@ -386,6 +386,10 @@ s:tab("faq", "FAQ") o = s:taboption("faq", DummyValue, "") o.template = appname .. "/global/faq" +s:tab("maintain", translate("Maintain")) +o = s:taboption("maintain", DummyValue, "") +o.template = appname .. "/global/backup" + -- [[ Socks Server ]]-- o = s:taboption("Main", Flag, "socks_enabled", "Socks " .. translate("Main switch")) o.rmempty = false diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/log.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/log.lua index 384cf1b315..a9102b6918 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/log.lua +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/log.lua @@ -1,70 +1,8 @@ -local api = require "luci.passwall2.api" local appname = "passwall2" -local http = require "luci.http" -local fs = api.fs -local sys = api.sys f = SimpleForm(appname) f.reset = false f.submit = false f:append(Template(appname .. "/log/log")) -fb = SimpleForm('backup-restore') -fb.reset = false -fb.submit = false -s = fb:section(SimpleSection, translate("Backup and Restore"), translate("Backup or Restore Client and Server Configurations.") .. - "
" .. - translate("Note: Restoring configurations across different versions may cause compatibility issues.") .. - "") - -s.anonymous = true -s:append(Template(appname .. "/log/backup_restore")) - -local backup_files = { - "/etc/config/passwall2", - "/etc/config/passwall2_server", - "/usr/share/passwall2/domains_excluded" -} - -local file_path = '/tmp/passwall2_upload.tar.gz' -local temp_dir = '/tmp/passwall2_bak' -local fd -http.setfilehandler(function(meta, chunk, eof) - if not fd and meta and meta.name == "ulfile" and chunk then - sys.call("rm -rf " .. temp_dir) - fs.remove(file_path) - fd = nixio.open(file_path, "w") - sys.call("echo '' > /tmp/log/passwall2.log") - end - if fd and chunk then - fd:write(chunk) - end - if eof and fd then - fd:close() - fd = nil - if fs.access(file_path) then - api.log(" * PassWall2 配置文件上传成功…") - sys.call("mkdir -p " .. temp_dir) - if sys.call("tar -xzf " .. file_path .. " -C " .. temp_dir) == 0 then - for _, backup_file in ipairs(backup_files) do - local temp_file = temp_dir .. backup_file - if fs.access(temp_file) then - sys.call("cp -f " .. temp_file .. " " .. backup_file) - end - end - api.log(" * PassWall2 配置还原成功…") - api.log(" * 重启 PassWall2 服务中…\n") - sys.call('/etc/init.d/passwall2 restart > /dev/null 2>&1 &') - sys.call('/etc/init.d/passwall2_server restart > /dev/null 2>&1 &') - else - api.log(" * PassWall2 配置文件解压失败,请重试!") - end - else - api.log(" * PassWall2 配置文件上传失败,请重试!") - end - sys.call("rm -rf " .. temp_dir) - fs.remove(file_path) - end -end) - -return f, fb +return f \ No newline at end of file diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua index e633f3fcfd..96b88eb315 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua @@ -53,6 +53,16 @@ function s.remove(e, t) m:set(s[".name"], "node", "default") end end) + if (m:get(t, "add_mode") or "0") == "2" then + local add_from = m:get(t, "add_from") or "" + if add_from ~= "" then + m.uci:foreach(appname, "subscribe_list", function(s) + if s["remark"] == add_from then + m:del(s[".name"], "md5") + end + end) + end + end TypedSection.remove(e, t) local new_node local node0 = m:get("@nodes[0]") or nil diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua index 54b8b6784f..154fe5e9e1 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua @@ -155,7 +155,10 @@ local pi = s:option(Value, _n("probeInterval"), translate("Probe Interval")) pi:depends({ [_n("balancingStrategy")] = "leastPing" }) pi:depends({ [_n("balancingStrategy")] = "leastLoad" }) pi.default = "1m" -pi.description = translate("The interval between initiating probes. The time format is numbers + units, such as '10s', '2h45m', and the supported time units are ns, us, ms, s, m, h, which correspond to nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively.") +pi.placeholder = "1m" +pi.description = translate("The interval between initiating probes.") .. "
" .. + translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. + translate("When the unit is not filled in, it defaults to seconds.") if api.compare_versions(xray_version, ">=", "1.8.12") then ucpu:depends({ [_n("protocol")] = "_balancing" }) diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/sing-box.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/sing-box.lua index 73d9e0973f..998e689c72 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/sing-box.lua +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/sing-box.lua @@ -126,20 +126,26 @@ o.description = translate("The URL used to detect the connection status.") o = s:option(Value, _n("urltest_interval"), translate("Test interval")) o:depends({ [_n("protocol")] = "_urltest" }) -o.datatype = "uinteger" -o.default = "180" -o.description = translate("The test interval in seconds.") .. "
" .. +o.default = "3m" +o.placeholder = "3m" +o.description = translate("The interval between initiating probes.") .. "
" .. + translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. + translate("When the unit is not filled in, it defaults to seconds.") .. "
" .. translate("Test interval must be less or equal than idle timeout.") o = s:option(Value, _n("urltest_tolerance"), translate("Test tolerance"), translate("The test tolerance in milliseconds.")) o:depends({ [_n("protocol")] = "_urltest" }) o.datatype = "uinteger" +o.placeholder = "50" o.default = "50" -o = s:option(Value, _n("urltest_idle_timeout"), translate("Idle timeout"), translate("The idle timeout in seconds.")) +o = s:option(Value, _n("urltest_idle_timeout"), translate("Idle timeout")) o:depends({ [_n("protocol")] = "_urltest" }) -o.datatype = "uinteger" -o.default = "1800" +o.placeholder = "30m" +o.default = "30m" +o.description = translate("The idle timeout.") .. "
" .. + translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. + translate("When the unit is not filled in, it defaults to seconds.") o = s:option(Flag, _n("urltest_interrupt_exist_connections"), translate("Interrupt existing connections")) o:depends({ [_n("protocol")] = "_urltest" }) diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ss-rust.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ss-rust.lua index 12ee72fdaf..fea94a1efc 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ss-rust.lua +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ss-rust.lua @@ -48,10 +48,12 @@ o:value("none", translate("none")) if api.is_finded("xray-plugin") then o:value("xray-plugin") end if api.is_finded("v2ray-plugin") then o:value("v2ray-plugin") end if api.is_finded("obfs-local") then o:value("obfs-local") end +if api.is_finded("shadow-tls") then o:value("shadow-tls") end o = s:option(Value, _n("plugin_opts"), translate("opts")) o:depends({ [_n("plugin")] = "xray-plugin"}) o:depends({ [_n("plugin")] = "v2ray-plugin"}) o:depends({ [_n("plugin")] = "obfs-local"}) +o:depends({ [_n("plugin")] = "shadow-tls"}) api.luci_types(arg[1], m, s, type_name, option_prefix) diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/api.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/api.lua index e514161d9e..f0de7e6328 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/api.lua +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/api.lua @@ -1285,3 +1285,32 @@ function luci_types(id, m, s, type_name, option_prefix) end end end +function format_go_time(input) + input = input and trim(input) + local N = 0 + if input and input:match("^%d+$") then + N = tonumber(input) + elseif input and input ~= "" then + for value, unit in input:gmatch("(%d+)%s*([hms])") do + value = tonumber(value) + if unit == "h" then + N = N + value * 3600 + elseif unit == "m" then + N = N + value * 60 + elseif unit == "s" then + N = N + value + end + end + end + if N <= 0 then + return "0s" + end + local result = "" + local h = math.floor(N / 3600) + local m = math.floor(N % 3600 / 60) + local s = N % 60 + if h > 0 then result = result .. h .. "h" end + if m > 0 then result = result .. m .. "m" end + if s > 0 or result == "" then result = result .. s .. "s" end + return result +end diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua index 576e34e3e4..85558f5e73 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua @@ -442,6 +442,7 @@ function gen_config_server(node) if node.tls == "1" and node.reality == "1" then tls.certificate_path = nil tls.key_path = nil + tls.server_name = node.reality_handshake_server tls.reality = { enabled = true, private_key = node.reality_private_key, @@ -991,9 +992,9 @@ function gen_config(var) tag = urltest_tag, outbounds = valid_nodes, url = _node.urltest_url or "https://www.gstatic.com/generate_204", - interval = _node.urltest_interval and tonumber(_node.urltest_interval) and string.format("%dm", tonumber(_node.urltest_interval) / 60) or "3m", - tolerance = _node.urltest_tolerance and tonumber(_node.urltest_tolerance) and tonumber(_node.urltest_tolerance) or 50, - idle_timeout = _node.urltest_idle_timeout and tonumber(_node.urltest_idle_timeout) and string.format("%dm", tonumber(_node.urltest_idle_timeout) / 60) or "30m", + interval = (api.format_go_time(_node.urltest_interval) ~= "0s") and api.format_go_time(_node.urltest_interval) or "3m", + tolerance = (_node.urltest_tolerance and tonumber(_node.urltest_tolerance) > 0) and tonumber(_node.urltest_tolerance) or 50, + idle_timeout = (api.format_go_time(_node.urltest_idle_timeout) ~= "0s") and api.format_go_time(_node.urltest_idle_timeout) or "30m", interrupt_exist_connections = (_node.urltest_interrupt_exist_connections == "true" or _node.urltest_interrupt_exist_connections == "1") and true or false } table.insert(outbounds, outbound) diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_xray.lua index 1effe25603..a8570ef036 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_xray.lua +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_xray.lua @@ -787,7 +787,7 @@ function gen_config(var) subjectSelector = { "blc-" }, pingConfig = { destination = _node.useCustomProbeUrl and _node.probeUrl or nil, - interval = _node.probeInterval or "1m", + interval = (api.format_go_time(_node.probeInterval) ~= "0s") and api.format_go_time(_node.probeInterval) or "1m", sampling = 3, timeout = "5s" } diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/global/backup.htm b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/global/backup.htm new file mode 100644 index 0000000000..67aca2b92d --- /dev/null +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/global/backup.htm @@ -0,0 +1,223 @@ +<% +local api = require "luci.passwall2.api" +-%> + +
+

<%:Backup and Restore%>

+
+ <%:Backup or Restore Client and Server Configurations.%> +
+ <%:Note: Restoring configurations across different versions may cause compatibility issues.%> +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ + + + + + diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/log/backup_restore.htm b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/log/backup_restore.htm deleted file mode 100644 index 4e4f2e26d5..0000000000 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/log/backup_restore.htm +++ /dev/null @@ -1,132 +0,0 @@ -<% -local api = require "luci.passwall2.api" --%> - -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- - - - - - diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm index 2215fa9648..ea37c28a33 100644 --- a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm @@ -54,13 +54,15 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin } function b64decsafe(str) { - var l; - str = str.replace(/-/g, "+").replace(/_/g, "/"); - l = str.length; - l = (4 - l % 4) % 4; - if (l) - str = padright(str, l, "="); - return atob(str); + const orig = str; + try { + str = str.replace(/-/g, "+").replace(/_/g, "/"); + const pad = (4 - str.length % 4) % 4; + if (pad) str += "=".repeat(pad); + return atob(str); + } catch (e) { + return orig; + } } function dictvalue(d, key) { @@ -106,24 +108,11 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin get: function (opt) { var id = this.base + "." + opt; var obj = document.getElementsByName(id)[0] || document.getElementsByClassName(id)[0] || document.getElementById(id) - // 如果找不到,返回一个模拟 DOM 的空对象,带常用方法和属性(防报错) - if (!obj) { - return { - value: "", - checked: false, - focus: function () {}, - blur: function () {}, - addEventListener: function () {}, - removeEventListener: function () {}, - classList: { - add: function () {}, - remove: function () {}, - toggle: function () {}, - contains: function () { return false; } - } - }; + if (obj) { + return obj; + } else { + return null; } - return obj; }, getlist: function (opt) { var id = this.base + "." + opt; @@ -206,6 +195,28 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin url = b64encsafe(v_method.value + ":" + v_password.value) + "@" + _address + ":" + v_port.value + "/?"; + + var shadow_tls; + //生成SS Shadow-TLS 插件参数 + const generateShadowTLSBase64 = function(paramStr) { + try { + let obj = {}; + let list = paramStr.split(";"); + for (let i = 0; i < list.length; i++) { + let kv = list[i].split("="); + if (kv.length === 2) { + let k = kv[0].trim(), v = kv[1].trim(); + let m = k.match(/^v(\d+)$/); + if (m && v === "1") obj.version = m[1]; + else if (k === "passwd") obj.password = v; + else obj[k] = v; + } + } + return b64encsafe(JSON.stringify(obj)); + } catch (e) { + return ""; + } + } var params = ""; var v_plugin_dom = opt.get(dom_prefix + "plugin"); @@ -220,8 +231,13 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin v_plugin += ";" + v_plugin_opts; } params += "&plugin=" + encodeURIComponent(v_plugin); + + if (v_plugin_dom.value == "shadow-tls" && v_plugin_opts && v_plugin_opts != "") { + params = "shadow-tls=" + generateShadowTLSBase64(v_plugin_opts); + shadow_tls = 1; + } } - } else { + } else if (v_type === "sing-box" || v_type === "Xray") { var v_transport = opt.get(dom_prefix + "transport").value; if (v_transport === "ws") { params += opt.query("host", dom_prefix + "ws_host"); @@ -274,8 +290,31 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin params += opt.query("alpn", dom_prefix + "alpn"); params += opt.query("sni", dom_prefix + "tls_serverName"); } + + if (opt.get(dom_prefix + "shadowtls")?.checked) { + let st_plugin_str = ""; + let st_version = opt.get(dom_prefix + "shadowtls_version")?.value; + if (st_version) st_plugin_str += "v" + st_version + "=1;"; + let st_password = opt.get(dom_prefix + "shadowtls_password")?.value; + if (st_password) st_plugin_str += "passwd=" + st_password +";"; + let st_host = opt.get(dom_prefix + "shadowtls_serverName")?.value; + if (st_host) st_plugin_str += "host=" + st_host +";"; + if (opt.get(dom_prefix + "shadowtls_utls").checked) { + let st_fingerprint = opt.get(dom_prefix + "shadowtls_fingerprint")?.value; + if (st_fingerprint) st_plugin_str += "fingerprint=" + st_fingerprint; + } + params = "shadow-tls=" + generateShadowTLSBase64(st_plugin_str); + shadow_tls = 1; + } + } + + if (shadow_tls) { + url = b64encsafe(v_method.value + ":" + v_password.value + "@" + + _address + ":" + + v_port.value) + "?"; + } else { + params += "&group=" } - params += "&group=" params += "#" + encodeURIComponent(v_alias.value); if (params[0] == "&") { params = params.substring(1); @@ -330,9 +369,9 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin v_transport = "kcp"; info.type = opt.get(dom_prefix + "mkcp_guise").value; } else if (v_transport === "quic") { - info.type = opt.get(dom_prefix + "quic_guise").value; - info.key = opt.get(dom_prefix + "quic_key").value; - info.securty = opt.get(dom_prefix + "quic_security").value; + info.type = opt.get(dom_prefix + "quic_guise")?.value; + info.key = opt.get(dom_prefix + "quic_key")?.value; + info.securty = opt.get(dom_prefix + "quic_security")?.value; } else if (v_transport === "grpc") { info.path = opt.get(dom_prefix + "grpc_serviceName").value; } @@ -772,232 +811,268 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin opt.set('remarks', b64decutf8safe(rem)); } if (ssu[0] === "ss") { - dom_prefix = "ss_" - var url0 = "", param = ""; - var sipIndex = ssu[1].indexOf("@"); - var ploc = ssu[1].indexOf("#"); - if (ploc > 0) { - url0 = ssu[1].substr(0, ploc); - param = ssu[1].substr(ploc + 1); - } else { - url0 = ssu[1]; + var url0 = ssu[1] || ""; + param = ""; + + var ploc = url0.indexOf("#"); + if (ploc >= 0) { + param = url0.substr(ploc + 1); + url0 = url0.substr(0, ploc); } + + var queryIndex = (url0 = url0.replace('/?', '?')).indexOf("?"); + var queryStr = ""; + if (queryIndex >= 0) { + queryStr = url0.substr(queryIndex + 1); + url0 = url0.substr(0, queryIndex); + } + var queryParam = {}; + queryParam = Object.fromEntries(new URLSearchParams(queryStr)); + + var server, port, method, password, plugin, pluginOpts; + var sipIndex = url0.indexOf("@"); if (sipIndex !== -1) { - // SIP002 + // SIP002 base64(method:pass)@host:port var userInfo = b64decsafe(decodeURIComponent(url0.substr(0, sipIndex))); - var temp = url0.substr(sipIndex + 1).replace('/?', '?').split('?'); - var serverInfo = temp[0].split(":"); - var server = serverInfo[0]; - var port = serverInfo[1]; - var method, password, plugin, pluginOpts; - var queryParam = {}; - if (temp[1]) { - var queryArray = temp[1].split('&'); - var params; - for (var i = 0; i < queryArray.length; i++) { - params = queryArray[i].split('='); - queryParam[decodeURIComponent(params[0])] = decodeURIComponent(params[1] || ''); - } - if (queryParam.plugin) { - var pluginInfo = decodeURIComponent(temp[1]); - var pluginIndex = pluginInfo.indexOf(";"); - var pluginNameInfo = pluginInfo.substr(0, pluginIndex); - plugin = pluginNameInfo.substr(pluginNameInfo.indexOf("=") + 1) - pluginOpts = pluginInfo.substr(pluginIndex + 1).split("&")[0]; - } - } + var temp = url0.substr(sipIndex + 1); + var serverInfo = temp.split(":"); + server = serverInfo[0]; + port = serverInfo[1]; var userInfoSplitIndex = userInfo.indexOf(":"); if (userInfoSplitIndex !== -1) { method = userInfo.substr(0, userInfoSplitIndex); password = userInfo.substr(userInfoSplitIndex + 1); } - if (ss_type == "sing-box" && has_singbox) { - dom_prefix = "singbox_" - opt.set('type', "sing-box"); - opt.set(dom_prefix + 'protocol', "shadowsocks"); - } else if (ss_type == "xray" && has_xray) { - dom_prefix = "xray_" - opt.set('type', "Xray"); - opt.set(dom_prefix + 'protocol', "shadowsocks"); - } else if (ss_type == "shadowsocks-rust") { + } else { + // base64(method:pass@host:port) + var sstr = b64decsafe(decodeURIComponent(url0)); + var m2022 = sstr.match(/^([^:]+):([^:]+):([^@]+)@([^:]+):(\d+)$/); + var mNormal = sstr.match(/^([^:]+):([^@]+)@([^:]+):(\d+)$/); + if (m2022) { + method = m2022[1]; + password = m2022[2] + ":" + m2022[3]; + server = m2022[4]; + port = m2022[5]; + } else if (mNormal) { + method = mNormal[1]; + password = mNormal[2]; + server = mNormal[3]; + port = mNormal[4]; + } + } + + // 判断密码是否经过url编码 + const isURLEncodedPassword = function(pwd) { + if (!/%[0-9A-Fa-f]{2}/.test(pwd)) return false; + try { + const decoded = decodeURIComponent(pwd.replace(/\+/g, "%20")); + const reencoded = encodeURIComponent(decoded); + return reencoded === pwd; + } catch (e) { + return false; + } + } + password = isURLEncodedPassword(password) ? decodeURIComponent(password) : password; + + if (queryParam.plugin) { + var pluginParams = decodeURIComponent(queryParam.plugin).split(";"); + plugin = pluginParams.shift(); + pluginOpts = pluginParams.join(";"); + } + + if (ss_type == "sing-box" && has_singbox) { + dom_prefix = "singbox_" + opt.set('type', "sing-box"); + opt.set(dom_prefix + 'protocol', "shadowsocks"); + } else if (ss_type == "xray" && has_xray) { + dom_prefix = "xray_" + opt.set('type', "Xray"); + opt.set(dom_prefix + 'protocol', "shadowsocks"); + } else if (ss_type == "shadowsocks-rust") { + dom_prefix = "ssrust_" + opt.set('type', "SS-Rust"); + } else { + if (["2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"].includes(method)) { dom_prefix = "ssrust_" opt.set('type', "SS-Rust"); - } else { - if (["2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"].includes(method)) { - dom_prefix = "ssrust_" - opt.set('type', "SS-Rust"); - } else { - dom_prefix = "ss_" - opt.set('type', "SS"); - } - } - if (ss_type !== "xray") { - method = method.toLowerCase() === "chacha20-poly1305" ? "chacha20-ietf-poly1305" : method; - method = method.toLowerCase() === "xchacha20-poly1305" ? "xchacha20-ietf-poly1305" : method; - } - opt.set(dom_prefix + 'address', server); - opt.set(dom_prefix + 'port', port); - opt.set(dom_prefix + 'password', password || ""); - opt.set(dom_prefix + 'method', method || ""); - opt.set(dom_prefix + 'ss_method', method || ""); - if (plugin && plugin != "none") { - plugin = (plugin === "simple-obfs") ? "obfs-local" : plugin; - opt.set(dom_prefix + 'plugin_enabled', true); - opt.set(dom_prefix + 'plugin', plugin || "none"); - opt.set(dom_prefix + 'plugin_opts', pluginOpts || ""); - //obfs-local插件转换成xray支持的格式 - if (plugin == "obfs-local" && dom_prefix == "xray_") { - var obfs = pluginOpts.match(/obfs=([^;]+)/); - var obfs_host = pluginOpts.match(/obfs-host=([^;]+)/); - obfs = obfs ? obfs[1] : ""; - obfs_host = obfs_host ? obfs_host[1] : ""; - if (obfs === "http") { - opt.set(dom_prefix + 'transport', "raw"); - opt.set(dom_prefix + 'tcp_guise', "http"); - opt.set(dom_prefix + 'tcp_guise_http_host', obfs_host || ''); - } else if (obfs === "tls") { - opt.set(dom_prefix + 'tls', true); - opt.set(dom_prefix + 'tls_serverName', obfs_host || ''); - opt.set(dom_prefix + 'tls_allowInsecure', true); - } - } - } - if (param !== undefined) { - opt.set('remarks', decodeURIComponent(param)); - } - if (!queryParam.plugin && (dom_prefix == "singbox_" || dom_prefix == "xray_")) { - opt.set(dom_prefix + 'encryption', queryParam.encryption); - if (queryParam.security) { - if (queryParam.security == "tls") { - opt.set(dom_prefix + 'tls', true); - opt.set(dom_prefix + 'reality', false); - opt.set(dom_prefix + 'flow', queryParam.flow || ''); - opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); - opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); - opt.set(dom_prefix + 'tls_allowInsecure', true); - if (queryParam.allowinsecure === '0' || queryParam.insecure === '0') { - opt.set(dom_prefix + 'tls_allowInsecure', false); - } - if (queryParam.fp && queryParam.fp.trim() != "") { - opt.set(dom_prefix + 'utls', true); - opt.set(dom_prefix + 'fingerprint', queryParam.fp); - } - } - - if (queryParam.security == "reality") { - opt.set(dom_prefix + 'tls', true); - opt.set(dom_prefix + 'reality', true); - opt.set(dom_prefix + 'flow', queryParam.flow || ''); - opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); - opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); - if (queryParam.fp && queryParam.fp.trim() != "") { - opt.set(dom_prefix + 'utls', true); - opt.set(dom_prefix + 'fingerprint', queryParam.fp); - } - opt.set(dom_prefix + 'reality_publicKey', queryParam.pbk || ''); - opt.set(dom_prefix + 'reality_shortId', queryParam.sid || ''); - opt.set(dom_prefix + 'reality_spiderX', queryParam.spx || ''); - } - - } - - queryParam.type = queryParam.type.toLowerCase(); - if (queryParam.type === "kcp" || queryParam.type === "mkcp") { - queryParam.type = "mkcp"; - } - if (queryParam.type === "h2" || queryParam.type === "http") { - queryParam.type = "http"; - } - if (dom_prefix == "singbox_" && queryParam.type === "raw") { - queryParam.type = "tcp"; - } else if (dom_prefix == "xray_" && queryParam.type === "tcp") { - queryParam.type = "raw"; - } - if (dom_prefix == "xray_" && queryParam.type === "http") { - opt.set(dom_prefix + 'transport', "xhttp"); - } else { - opt.set(dom_prefix + 'transport', queryParam.type); - } - if (queryParam.type === "raw" || queryParam.type === "tcp") { - opt.set(dom_prefix + 'tcp_guise', queryParam.headerType || "none"); - if (queryParam.headerType && queryParam.headerType != "none") { - opt.set(dom_prefix + 'tcp_guise_http_host', queryParam.host || ""); - opt.set(dom_prefix + 'tcp_guise_http_path', queryParam.path || ""); - } - } else if (queryParam.type === "ws") { - opt.set(dom_prefix + 'ws_host', queryParam.host || ""); - opt.set(dom_prefix + 'ws_path', queryParam.path || ""); - if (dom_prefix == "singbox_" && queryParam.path && queryParam.path.length > 1) { - var ws_path_params = {}; - var ws_path_dat = queryParam.path.split('?'); - var ws_path = ws_path_dat[0]; - var ws_path_params = {}; - var ws_path_params_array = (ws_path_dat[1] || '').split('&'); - for (i = 0; i < ws_path_params_array.length; i++) { - var kv = ws_path_params_array[i].split('='); - ws_path_params[decodeURIComponent(kv[0]).toLowerCase()] = decodeURIComponent(kv[1] || ''); - } - - if (ws_path_params.ed) { - opt.set(dom_prefix + 'ws_path', ws_path); - opt.set(dom_prefix + 'ws_enableEarlyData', true); - opt.set(dom_prefix + 'ws_maxEarlyData', ws_path_params.ed); - opt.set(dom_prefix + 'ws_earlyDataHeaderName', 'Sec-WebSocket-Protocol'); - } - } - } else if (queryParam.type === "h2" || queryParam.type === "http") { - if (dom_prefix == "xray_") { - opt.set(dom_prefix + 'xhttp_mode', "stream-one"); - opt.set(dom_prefix + 'xhttp_host', queryParam.host || ""); - opt.set(dom_prefix + 'xhttp_path', queryParam.path || ""); - } else { - opt.set(dom_prefix + 'http_host', queryParam.host || ""); - opt.set(dom_prefix + 'http_path', queryParam.path || ""); - } - } else if (queryParam.type === "quic") { - opt.set(dom_prefix + 'quic_guise', queryParam.headerType || "none"); - opt.set(dom_prefix + 'quic_security', queryParam.quicSecurity); - opt.set(dom_prefix + 'quic_key', queryParam.key); - } else if (queryParam.type === "kcp" || queryParam.type === "mkcp") { - opt.set(dom_prefix + 'mkcp_guise', queryParam.headerType || "none"); - } else if (queryParam.type === "grpc") { - opt.set(dom_prefix + 'grpc_serviceName', (queryParam.serviceName || queryParam.path) || ""); - opt.set(dom_prefix + 'grpc_mode', queryParam.mode || "gun"); - } - } - } else { - var sstr = b64decsafe(url0); - var team = sstr.split('@'); - var part1 = team[0].split(':'); - var part2 = team[1].split(':'); - var method = part1[0] - - if (ss_type == "sing-box" && has_singbox) { - dom_prefix = "singbox_" - opt.set('type', "sing-box"); - opt.set(dom_prefix + 'protocol', "shadowsocks"); - } else if (ss_type == "xray" && has_xray) { - dom_prefix = "xray_" - opt.set('type', "Xray"); - opt.set(dom_prefix + 'protocol', "shadowsocks"); } else { dom_prefix = "ss_" opt.set('type', "SS"); } - - if (ss_type !== "xray") { - method = method.toLowerCase() === "chacha20-poly1305" ? "chacha20-ietf-poly1305" : method; - method = method.toLowerCase() === "xchacha20-poly1305" ? "xchacha20-ietf-poly1305" : method; + } + if (ss_type !== "xray") { + method = method.toLowerCase() === "chacha20-poly1305" ? "chacha20-ietf-poly1305" : method; + method = method.toLowerCase() === "xchacha20-poly1305" ? "xchacha20-ietf-poly1305" : method; + } + opt.set(dom_prefix + 'address', server); + opt.set(dom_prefix + 'port', port); + opt.set(dom_prefix + 'password', password || ""); + opt.set(dom_prefix + 'method', method || ""); + opt.set(dom_prefix + 'ss_method', method || ""); + if (plugin && plugin != "none") { + plugin = (plugin === "simple-obfs") ? "obfs-local" : plugin; + opt.set(dom_prefix + 'plugin_enabled', true); + opt.set(dom_prefix + 'plugin', plugin || "none"); + opt.set(dom_prefix + 'plugin_opts', pluginOpts || ""); + //obfs-local插件转换成xray支持的格式 + if (plugin == "obfs-local" && dom_prefix == "xray_") { + var obfs = pluginOpts.match(/obfs=([^;]+)/); + var obfs_host = pluginOpts.match(/obfs-host=([^;]+)/); + obfs = obfs ? obfs[1] : ""; + obfs_host = obfs_host ? obfs_host[1] : ""; + if (obfs === "http") { + opt.set(dom_prefix + 'transport', "raw"); + opt.set(dom_prefix + 'tcp_guise', "http"); + opt.set(dom_prefix + 'tcp_guise_http_host', obfs_host || ''); + } else if (obfs === "tls") { + opt.set(dom_prefix + 'tls', true); + opt.set(dom_prefix + 'tls_serverName', obfs_host || ''); + opt.set(dom_prefix + 'tls_allowInsecure', true); + } } - opt.set(dom_prefix + 'address', part2[0]); - opt.set(dom_prefix + 'port', part2[1]); - opt.set(dom_prefix + 'password', part1[1]); - opt.set(dom_prefix + 'method', method); - opt.set(dom_prefix + 'ss_method', method); + } else { opt.set(dom_prefix + 'plugin', "none"); - //opt.set(dom_prefix + 'plugin_opts', ""); - if (param !== undefined) { - opt.set('remarks', decodeURIComponent(param)); + } + if (param !== undefined) { + opt.set('remarks', decodeURIComponent(param)); + } + + if (Object.keys(queryParam).length > 0 && !queryParam.plugin) { + opt.set(dom_prefix + 'encryption', queryParam.encryption); + if (queryParam.security) { + if (queryParam.security == "tls") { + opt.set(dom_prefix + 'tls', true); + opt.set(dom_prefix + 'reality', false); + opt.set(dom_prefix + 'flow', queryParam.flow || ''); + opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); + opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); + opt.set(dom_prefix + 'tls_allowInsecure', true); + if (queryParam.allowinsecure === '0' || queryParam.insecure === '0') { + opt.set(dom_prefix + 'tls_allowInsecure', false); + } + if (queryParam.fp && queryParam.fp.trim() != "") { + opt.set(dom_prefix + 'utls', true); + opt.set(dom_prefix + 'fingerprint', queryParam.fp); + } + } + + if (queryParam.security == "reality") { + opt.set(dom_prefix + 'tls', true); + opt.set(dom_prefix + 'reality', true); + opt.set(dom_prefix + 'flow', queryParam.flow || ''); + opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); + opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); + if (queryParam.fp && queryParam.fp.trim() != "") { + opt.set(dom_prefix + 'utls', true); + opt.set(dom_prefix + 'fingerprint', queryParam.fp); + } + opt.set(dom_prefix + 'reality_publicKey', queryParam.pbk || ''); + opt.set(dom_prefix + 'reality_shortId', queryParam.sid || ''); + opt.set(dom_prefix + 'reality_spiderX', queryParam.spx || ''); + } + } + + queryParam.type = queryParam.type?.toLowerCase(); + if (queryParam.type === "kcp") { + queryParam.type = "mkcp"; + } + if (queryParam.type === "h2") { + queryParam.type = "http"; + } + if (dom_prefix == "singbox_" && queryParam.type === "raw") { + queryParam.type = "tcp"; + } else if (dom_prefix == "xray_" && queryParam.type === "tcp") { + queryParam.type = "raw"; + } + if (dom_prefix == "xray_" && queryParam.type === "http") { + opt.set(dom_prefix + 'transport', "xhttp"); + } else { + opt.set(dom_prefix + 'transport', queryParam.type); + } + if (queryParam.type === "raw" || queryParam.type === "tcp") { + opt.set(dom_prefix + 'tcp_guise', queryParam.headerType || "none"); + if (queryParam.headerType && queryParam.headerType != "none") { + opt.set(dom_prefix + 'tcp_guise_http_host', queryParam.host || ""); + opt.set(dom_prefix + 'tcp_guise_http_path', queryParam.path || ""); + } + } else if (queryParam.type === "ws") { + opt.set(dom_prefix + 'ws_host', queryParam.host || ""); + opt.set(dom_prefix + 'ws_path', queryParam.path || ""); + if (dom_prefix == "singbox_" && queryParam.path && queryParam.path.length > 1) { + var ws_path_params = {}; + var ws_path_dat = queryParam.path.split('?'); + var ws_path = ws_path_dat[0]; + var ws_path_params = {}; + var ws_path_params_array = (ws_path_dat[1] || '').split('&'); + for (i = 0; i < ws_path_params_array.length; i++) { + var kv = ws_path_params_array[i].split('='); + ws_path_params[decodeURIComponent(kv[0]).toLowerCase()] = decodeURIComponent(kv[1] || ''); + } + + if (ws_path_params.ed) { + opt.set(dom_prefix + 'ws_path', ws_path); + opt.set(dom_prefix + 'ws_enableEarlyData', true); + opt.set(dom_prefix + 'ws_maxEarlyData', ws_path_params.ed); + opt.set(dom_prefix + 'ws_earlyDataHeaderName', 'Sec-WebSocket-Protocol'); + } + } + } else if (queryParam.type === "http") { + if (dom_prefix == "xray_") { + opt.set(dom_prefix + 'xhttp_mode', "stream-one"); + opt.set(dom_prefix + 'xhttp_host', queryParam.host || ""); + opt.set(dom_prefix + 'xhttp_path', queryParam.path || ""); + } else { + opt.set(dom_prefix + 'http_host', queryParam.host || ""); + opt.set(dom_prefix + 'http_path', queryParam.path || ""); + } + } else if (queryParam.type === "quic") { + opt.set(dom_prefix + 'quic_guise', queryParam.headerType || "none"); + opt.set(dom_prefix + 'quic_security', queryParam.quicSecurity); + opt.set(dom_prefix + 'quic_key', queryParam.key); + } else if (queryParam.type === "mkcp") { + opt.set(dom_prefix + 'mkcp_guise', queryParam.headerType || "none"); + } else if (queryParam.type === "grpc") { + opt.set(dom_prefix + 'grpc_serviceName', (queryParam.serviceName || queryParam.path) || ""); + opt.set(dom_prefix + 'grpc_mode', queryParam.mode || "gun"); + } + + if (queryParam["shadow-tls"]) { + //解析SS Shadow-TLS 插件参数 + const parseShadowTLSParams = function(base64Str, outObj) { + try { + let obj = JSON.parse(b64decsafe(base64Str)); + if (outObj && typeof outObj === "object") { + for (let k in obj) outObj[k] = obj[k]; + } + let out = []; + if (obj.version) out.push("v" + obj.version + "=1"); + if (obj.password) out.push("passwd=" + obj.password); + for (let k in obj) + if (k !== "version" && k !== "password") + out.push(k + "=" + obj[k]); + return out.join(";"); + } catch (e) { + return ""; + } + } + if (dom_prefix === "ssrust_") { + opt.set(dom_prefix + 'plugin', "shadow-tls"); + let shadowtlsOpt = parseShadowTLSParams(queryParam["shadow-tls"]); + opt.set(dom_prefix + 'plugin_opts', shadowtlsOpt || ""); + } else if (dom_prefix === "singbox_") { + let shadowtlsOpt = {}; + parseShadowTLSParams(queryParam["shadow-tls"], shadowtlsOpt); + if (Object.keys(shadowtlsOpt).length > 0) { + opt.set(dom_prefix + 'shadowtls', true); + opt.set(dom_prefix + 'shadowtls_version', shadowtlsOpt.version || "1"); + opt.set(dom_prefix + 'shadowtls_password', shadowtlsOpt.password || ""); + opt.set(dom_prefix + 'shadowtls_serverName', shadowtlsOpt.host || ""); + if (shadowtlsOpt.fingerprint) { + opt.set(dom_prefix + 'shadowtls_utls', true); + opt.set(dom_prefix + 'shadowtls_fingerprint', shadowtlsOpt.fingerprint || "chrome"); + } + } + } } } } @@ -1372,17 +1447,17 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin dom_prefix = "singbox_" opt.set(dom_prefix + 'protocol', "hysteria2"); opt.set(dom_prefix + 'hysteria2_auth_password', decodeURIComponent(password)); - if (queryParam["obfs-password"]) { + if (queryParam["obfs-password"] || queryParam["obfs_password"]) { opt.set(dom_prefix + 'hysteria2_obfs_type', "salamander"); - opt.set(dom_prefix + 'hysteria2_obfs_password', queryParam["obfs-password"]); + opt.set(dom_prefix + 'hysteria2_obfs_password', queryParam["obfs-password"] || queryParam["obfs_password"]); } opt.set(dom_prefix + 'hysteria2_hop', queryParam.mport || ""); } else if (has_hysteria2) { opt.set('type', "Hysteria2"); dom_prefix = "hysteria2_" opt.set(dom_prefix + 'auth_password', decodeURIComponent(password)); - if (queryParam["obfs-password"]) { - opt.set(dom_prefix + 'obfs', queryParam["obfs-password"]); + if (queryParam["obfs-password"] || queryParam["obfs_password"]) { + opt.set(dom_prefix + 'obfs', queryParam["obfs-password"] || queryParam["obfs_password"]); } if (queryParam.pinSHA256) { opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pinSHA256); diff --git a/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/rule/geoview.htm b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/rule/geoview.htm new file mode 100644 index 0000000000..efe98ef16e --- /dev/null +++ b/openwrt-passwall2/luci-app-passwall2/luasrc/view/passwall2/rule/geoview.htm @@ -0,0 +1,96 @@ +<% +local api = require "luci.passwall2.api" +-%> + + + +
+ +
+
+
+ + +
+
+ <%:Enter a domain or IP to query the Geo rule list they belong to.%> +
+
+
+
+
+ + +
+
+ <%:Enter a GeoIP or Geosite to extract the domains/IPs they contain. Format: geoip:cn or geosite:gfw%> +
+
+
+
+ +
+ + diff --git a/openwrt-passwall2/luci-app-passwall2/po/zh-cn/passwall2.po b/openwrt-passwall2/luci-app-passwall2/po/zh-cn/passwall2.po index 1344d9bdb3..9c0a33b080 100644 --- a/openwrt-passwall2/luci-app-passwall2/po/zh-cn/passwall2.po +++ b/openwrt-passwall2/luci-app-passwall2/po/zh-cn/passwall2.po @@ -46,6 +46,9 @@ msgstr "规则列表" msgid "Access control" msgstr "访问控制" +msgid "Watch Logs" +msgstr "查看日志" + msgid "Node Config" msgstr "节点配置" @@ -355,8 +358,14 @@ msgstr "用于检测连接状态的网址。" msgid "Probe Interval" msgstr "探测间隔" -msgid "The interval between initiating probes. The time format is numbers + units, such as '10s', '2h45m', and the supported time units are ns, us, ms, s, m, h, which correspond to nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively." -msgstr "发起探测的间隔。时间格式为数字+单位,比如"10s", "2h45m",支持的时间单位有 nsusmssmh,分别对应纳秒、微秒、毫秒、秒、分、时。" +msgid "The interval between initiating probes." +msgstr "发起探测的间隔。" + +msgid "The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively." +msgstr "时间格式为数字+单位,比如"10s", "2h45m",支持的时间单位有 smh,分别对应秒、分、时。" + +msgid "When the unit is not filled in, it defaults to seconds." +msgstr "未填写单位时,默认为秒。" msgid "Preferred Node Count" msgstr "优选节点数量" @@ -1615,8 +1624,8 @@ msgstr "仅 IPv4" msgid "IPv6 Only" msgstr "仅 IPv6" -msgid "Log Maint" -msgstr "日志维护" +msgid "Maintain" +msgstr "维护" msgid "Backup and Restore" msgstr "备份还原" @@ -1651,6 +1660,15 @@ msgstr "恢复默认配置" msgid "Do Reset" msgstr "执行重置" +msgid "Please select a file first." +msgstr "请先选择一个文件。" + +msgid "Invalid file type. Please upload a .tar.gz file." +msgstr "文件类型无效,请上传一个 .tar.gz 文件。" + +msgid "File size exceeds 10MB limit." +msgstr "文件大小超过 10MB 限制。" + msgid "Do you want to restore the client to default settings?" msgstr "是否要恢复客户端默认配置?" @@ -1669,9 +1687,6 @@ msgstr "要测试的节点列表,/dev/null 2>&1 unset V2RAY_LOCATION_ASSET diff --git a/openwrt-passwall2/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua b/openwrt-passwall2/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua index d3f241a150..f69c22e2b4 100755 --- a/openwrt-passwall2/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua +++ b/openwrt-passwall2/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua @@ -400,18 +400,16 @@ do end end --- urlencode --- local function get_urlencode(c) return sformat("%%%02X", sbyte(c)) end +local function UrlEncode(szText) + return szText:gsub("([^%w%-_%.%~])", function(c) + return string.format("%%%02X", string.byte(c)) + end) +end --- local function urlEncode(szText) --- local str = szText:gsub("([^0-9a-zA-Z ])", get_urlencode) --- str = str:gsub(" ", "+") --- return str --- end - -local function get_urldecode(h) return schar(tonumber(h, 16)) end local function UrlDecode(szText) - return (szText and szText:gsub("+", " "):gsub("%%(%x%x)", get_urldecode)) or nil + return szText and szText:gsub("+", " "):gsub("%%(%x%x)", function(h) + return string.char(tonumber(h, 16)) + end) or nil end -- trim @@ -611,10 +609,9 @@ local function processData(szType, content, add_mode, add_from) --ss://2022-blake3-aes-256-gcm:YctPZ6U7xPPcU%2Bgp3u%2B0tx%2FtRizJN9K8y%2BuKlW2qjlI%3D@192.168.100.1:8888/?plugin=v2ray-plugin%3Bserver#Example3 --ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTp0ZXN0@xxxxxx.com:443?type=ws&path=%2Ftestpath&host=xxxxxx.com&security=tls&fp=&alpn=h3%2Ch2%2Chttp%2F1.1&sni=xxxxxx.com#test-1%40ss - local idx_sp = 0 + local idx_sp = content:find("#") or 0 local alias = "" - if content:find("#") then - idx_sp = content:find("#") + if idx_sp > 0 then alias = content:sub(idx_sp + 1, -1) end result.remarks = UrlDecode(alias) @@ -625,7 +622,7 @@ local function processData(szType, content, add_mode, add_from) local query = split(info, "%?") for _, v in pairs(split(query[2], '&')) do local t = split(v, '=') - params[t[1]] = UrlDecode(t[2]) + if #t >= 2 then params[t[1]] = UrlDecode(t[2]) end end if params.plugin then local plugin_info = params.plugin @@ -671,9 +668,22 @@ local function processData(szType, content, add_mode, add_from) else userinfo = base64Decode(hostInfo[1]) end - local method = userinfo:sub(1, userinfo:find(":") - 1) local password = userinfo:sub(userinfo:find(":") + 1, #userinfo) + + -- 判断密码是否经过url编码 + local function isURLEncodedPassword(pwd) + if not pwd:find("%%[0-9A-Fa-f][0-9A-Fa-f]") then + return false + end + local ok, decoded = pcall(UrlDecode, pwd) + return ok and UrlEncode(decoded) == pwd + end + + local decoded = UrlDecode(password) + if isURLEncodedPassword(password) and decoded then + password = decoded + end result.method = method result.password = password @@ -835,6 +845,48 @@ local function processData(szType, content, add_mode, add_from) result.error_msg = "请更换Xray或Sing-Box来支持SS更多的传输方式." end end + + if params["shadow-tls"] then + if result.type ~= "sing-box" and result.type ~= "SS-Rust" then + result.error_msg = ss_type_default .. " 不支持 shadow-tls 插件." + else + -- 解析SS Shadow-TLS 插件参数 + local function parseShadowTLSParams(b64str, out) + local ok, data = pcall(jsonParse, base64Decode(b64str)) + if not ok or type(data) ~= "table" then return "" end + if type(out) == "table" then + for k, v in pairs(data) do out[k] = v end + end + local t = {} + if data.version then t[#t+1] = "v" .. data.version .. "=1" end + if data.password then t[#t+1] = "passwd=" .. data.password end + for k, v in pairs(data) do + if k ~= "version" and k ~= "password" then + t[#t+1] = k .. "=" .. tostring(v) + end + end + return table.concat(t, ";") + end + + if result.type == "SS-Rust" then + result.plugin = "shadow-tls" + result.plugin_opts = parseShadowTLSParams(params["shadow-tls"]) + elseif result.type == "sing-box" then + local shadowtlsOpt = {} + parseShadowTLSParams(params["shadow-tls"], shadowtlsOpt) + if next(shadowtlsOpt) then + result.shadowtls = "1" + result.shadowtls_version = shadowtlsOpt.version or "1" + result.shadowtls_password = shadowtlsOpt.password + result.shadowtls_serverName = shadowtlsOpt.host + if shadowtlsOpt.fingerprint then + result.shadowtls_utls = "1" + result.shadowtls_fingerprint = shadowtlsOpt.fingerprint or "chrome" + end + end + end + end + end end elseif szType == "trojan" then if trojan_type_default == "sing-box" and has_singbox then @@ -1275,14 +1327,14 @@ local function processData(szType, content, add_mode, add_from) if hysteria2_type_default == "sing-box" and has_singbox then result.type = 'sing-box' result.protocol = "hysteria2" - if params["obfs-password"] then + if params["obfs-password"] or params["obfs_password"] then result.hysteria2_obfs_type = "salamander" - result.hysteria2_obfs_password = params["obfs-password"] + result.hysteria2_obfs_password = params["obfs-password"] or params["obfs_password"] end elseif has_hysteria2 then result.type = "Hysteria2" - if params["obfs-password"] then - result.hysteria2_obfs = params["obfs-password"] + if params["obfs-password"] or params["obfs_password"] then + result.hysteria2_obfs = params["obfs-password"] or params["obfs_password"] end end elseif szType == 'tuic' then diff --git a/small/luci-app-bypass/Makefile b/small/luci-app-bypass/Makefile index 4dad863fc7..98ba96401d 100644 --- a/small/luci-app-bypass/Makefile +++ b/small/luci-app-bypass/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-bypass PKG_VERSION:=1.2 -PKG_RELEASE:=20 +PKG_RELEASE:=21 PKG_CONFIG_DEPENDS:= \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server \ diff --git a/small/luci-app-bypass/root/etc/uci-defaults/luci-bypass b/small/luci-app-bypass/root/etc/uci-defaults/luci-bypass index 2303198ed6..df5db43c33 100644 --- a/small/luci-app-bypass/root/etc/uci-defaults/luci-bypass +++ b/small/luci-app-bypass/root/etc/uci-defaults/luci-bypass @@ -1,4 +1,3 @@ -uci batch < /usr/share/ucitrack/luci-app-bypass.json << EEOF { @@ -7,6 +6,7 @@ uci batch < /dev/null 2>&1); then log "Profile" "Subscription is valid." diff --git a/xray-core/app/proxyman/command/command.go b/xray-core/app/proxyman/command/command.go index 227a95c34c..a86f09212a 100644 --- a/xray-core/app/proxyman/command/command.go +++ b/xray-core/app/proxyman/command/command.go @@ -103,13 +103,22 @@ func (s *handlerServer) AlterInbound(ctx context.Context, request *AlterInboundR func (s *handlerServer) ListInbounds(ctx context.Context, request *ListInboundsRequest) (*ListInboundsResponse, error) { handlers := s.ihm.ListHandlers(ctx) response := &ListInboundsResponse{} - for _, handler := range handlers { - response.Inbounds = append(response.Inbounds, &core.InboundHandlerConfig{ - Tag: handler.Tag(), - ReceiverSettings: handler.ReceiverSettings(), - ProxySettings: handler.ProxySettings(), - }) + if request.GetIsOnlyTags() { + for _, handler := range handlers { + response.Inbounds = append(response.Inbounds, &core.InboundHandlerConfig{ + Tag: handler.Tag(), + }) + } + } else { + for _, handler := range handlers { + response.Inbounds = append(response.Inbounds, &core.InboundHandlerConfig{ + Tag: handler.Tag(), + ReceiverSettings: handler.ReceiverSettings(), + ProxySettings: handler.ProxySettings(), + }) + } } + return response, nil } diff --git a/xray-core/app/proxyman/command/command.pb.go b/xray-core/app/proxyman/command/command.pb.go index f4219d9193..6c1279601e 100644 --- a/xray-core/app/proxyman/command/command.pb.go +++ b/xray-core/app/proxyman/command/command.pb.go @@ -368,6 +368,8 @@ type ListInboundsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + IsOnlyTags bool `protobuf:"varint,1,opt,name=isOnlyTags,proto3" json:"isOnlyTags,omitempty"` } func (x *ListInboundsRequest) Reset() { @@ -400,6 +402,13 @@ func (*ListInboundsRequest) Descriptor() ([]byte, []int) { return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{8} } +func (x *ListInboundsRequest) GetIsOnlyTags() bool { + if x != nil { + return x.IsOnlyTags + } + return false +} + type ListInboundsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -993,9 +1002,11 @@ var file_app_proxyman_command_command_proto_rawDesc = []byte{ 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x16, 0x0a, 0x14, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x62, - 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x0a, 0x13, + 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x22, 0x53, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x62, 0x6f, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x54, 0x61, 0x67, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x54, + 0x61, 0x67, 0x73, 0x22, 0x53, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x08, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, diff --git a/xray-core/app/proxyman/command/command.proto b/xray-core/app/proxyman/command/command.proto index 4ff499eb22..71f8f0dcf0 100644 --- a/xray-core/app/proxyman/command/command.proto +++ b/xray-core/app/proxyman/command/command.proto @@ -37,7 +37,9 @@ message AlterInboundRequest { message AlterInboundResponse {} -message ListInboundsRequest {} +message ListInboundsRequest { + bool isOnlyTags = 1; +} message ListInboundsResponse { repeated core.InboundHandlerConfig inbounds = 1; diff --git a/xray-core/go.mod b/xray-core/go.mod index 2b589d1c20..efe9ac451f 100644 --- a/xray-core/go.mod +++ b/xray-core/go.mod @@ -9,7 +9,7 @@ require ( github.com/golang/mock v1.7.0-rc.1 github.com/google/go-cmp v0.7.0 github.com/gorilla/websocket v1.5.3 - github.com/miekg/dns v1.1.66 + github.com/miekg/dns v1.1.67 github.com/pelletier/go-toml v1.9.5 github.com/pires/go-proxyproto v0.8.1 github.com/quic-go/quic-go v0.52.0 @@ -22,10 +22,10 @@ require ( github.com/vishvananda/netlink v1.3.1 github.com/xtls/reality v0.0.0-20250608132114-50752aec6bfb go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.39.0 + golang.org/x/crypto v0.40.0 golang.org/x/net v0.41.0 - golang.org/x/sync v0.15.0 - golang.org/x/sys v0.33.0 + golang.org/x/sync v0.16.0 + golang.org/x/sys v0.34.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 google.golang.org/grpc v1.73.0 google.golang.org/protobuf v1.36.6 @@ -51,9 +51,9 @@ require ( github.com/vishvananda/netns v0.0.5 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/mod v0.25.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/text v0.27.0 // indirect golang.org/x/time v0.7.0 // indirect - golang.org/x/tools v0.33.0 // indirect + golang.org/x/tools v0.34.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/xray-core/go.sum b/xray-core/go.sum index c0fb7cb878..e2958d6633 100644 --- a/xray-core/go.sum +++ b/xray-core/go.sum @@ -40,8 +40,8 @@ github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0N github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= -github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= +github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0= +github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= @@ -99,8 +99,8 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= @@ -111,8 +111,8 @@ golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -121,21 +121,21 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/xray-core/main/commands/all/api/inbounds_list.go b/xray-core/main/commands/all/api/inbounds_list.go index e4267a6b6e..6060074ca0 100644 --- a/xray-core/main/commands/all/api/inbounds_list.go +++ b/xray-core/main/commands/all/api/inbounds_list.go @@ -7,7 +7,7 @@ import ( var cmdListInbounds = &base.Command{ CustomFlags: true, - UsageLine: "{{.Exec}} api lsi [--server=127.0.0.1:8080]", + UsageLine: "{{.Exec}} api lsi [--server=127.0.0.1:8080] [--isOnlyTags=true]", Short: "List inbounds", Long: ` List inbounds in Xray. @@ -29,14 +29,17 @@ Example: func executeListInbounds(cmd *base.Command, args []string) { setSharedFlags(cmd) + var isOnlyTagsStr string + cmd.Flag.StringVar(&isOnlyTagsStr, "isOnlyTags", "", "") cmd.Flag.Parse(args) + isOnlyTags := isOnlyTagsStr == "true" conn, ctx, close := dialAPIServer() defer close() client := handlerService.NewHandlerServiceClient(conn) - resp, err := client.ListInbounds(ctx, &handlerService.ListInboundsRequest{}) + resp, err := client.ListInbounds(ctx, &handlerService.ListInboundsRequest{IsOnlyTags: isOnlyTags}) if err != nil { base.Fatalf("failed to list inbounds: %s", err) } diff --git a/yt-dlp/README.md b/yt-dlp/README.md index e476c0084b..c1a9356923 100644 --- a/yt-dlp/README.md +++ b/yt-dlp/README.md @@ -1799,6 +1799,7 @@ The following extractors use this feature: * `skip`: One or more of `hls`, `dash` or `translated_subs` to skip extraction of the m3u8 manifests, dash manifests and [auto-translated subtitles](https://github.com/yt-dlp/yt-dlp/issues/4090#issuecomment-1158102032) respectively * `player_client`: Clients to extract video data from. The currently available clients are `web`, `web_safari`, `web_embedded`, `web_music`, `web_creator`, `mweb`, `ios`, `android`, `android_vr`, `tv`, `tv_simply` and `tv_embedded`. By default, `tv,ios,web` is used, or `tv,web` is used when authenticating with cookies. The `web_music` client is added for `music.youtube.com` URLs when logged-in cookies are used. The `web_embedded` client is added for age-restricted videos but only works if the video is embeddable. The `tv_embedded` and `web_creator` clients are added for age-restricted videos if account age-verification is required. Some clients, such as `web` and `web_music`, require a `po_token` for their formats to be downloadable. Some clients, such as `web_creator`, will only work with authentication. Not all clients support authentication via cookies. You can use `default` for the default clients, or you can use `all` for all clients (not recommended). You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=default,-ios` * `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player), `initial_data` (skip initial data/next ep request). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause issues such as missing formats or metadata. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) and [#12826](https://github.com/yt-dlp/yt-dlp/issues/12826) for more details +* `webpage_skip`: Skip extraction of embedded webpage data. One or both of `player_response`, `initial_data`. These options are for testing purposes and don't skip any network requests * `player_params`: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp. * `player_js_variant`: The player javascript variant to use for signature and nsig deciphering. The known variants are: `main`, `tce`, `tv`, `tv_es6`, `phone`, `tablet`. Only `main` is recommended as a possible workaround; the others are for debugging purposes. The default is to use what is prescribed by the site, and can be selected with `actual` * `comment_sort`: `top` or `new` (default) - choose comment sorting mode (on YouTube's side) diff --git a/yt-dlp/yt_dlp/extractor/youtube/_base.py b/yt-dlp/yt_dlp/extractor/youtube/_base.py index 5aee89b917..7d9cbf8ee4 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/_base.py +++ b/yt-dlp/yt_dlp/extractor/youtube/_base.py @@ -1,5 +1,6 @@ import calendar import copy +import dataclasses import datetime as dt import enum import functools @@ -38,6 +39,60 @@ class _PoTokenContext(enum.Enum): SUBS = 'subs' +class StreamingProtocol(enum.Enum): + HTTPS = 'https' + DASH = 'dash' + HLS = 'hls' + + +@dataclasses.dataclass +class BasePoTokenPolicy: + required: bool = False + # Try to fetch a PO Token even if it is not required. + recommended: bool = False + not_required_for_premium: bool = False + + +@dataclasses.dataclass +class GvsPoTokenPolicy(BasePoTokenPolicy): + not_required_with_player_token: bool = False + + +@dataclasses.dataclass +class PlayerPoTokenPolicy(BasePoTokenPolicy): + pass + + +@dataclasses.dataclass +class SubsPoTokenPolicy(BasePoTokenPolicy): + pass + + +WEB_PO_TOKEN_POLICIES = { + 'GVS_PO_TOKEN_POLICY': { + StreamingProtocol.HTTPS: GvsPoTokenPolicy( + required=True, + recommended=True, + not_required_for_premium=True, + not_required_with_player_token=False, + ), + StreamingProtocol.DASH: GvsPoTokenPolicy( + required=True, + recommended=True, + not_required_for_premium=True, + not_required_with_player_token=False, + ), + StreamingProtocol.HLS: GvsPoTokenPolicy( + required=False, + recommended=True, + ), + }, + 'PLAYER_PO_TOKEN_POLICY': PlayerPoTokenPolicy(required=False), + # In rollout, currently detected via experiment + # Premium users DO require a PO Token for subtitles + 'SUBS_PO_TOKEN_POLICY': SubsPoTokenPolicy(required=False), +} + # any clients starting with _ cannot be explicitly requested by the user INNERTUBE_CLIENTS = { 'web': { @@ -48,8 +103,8 @@ INNERTUBE_CLIENTS = { }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 1, - 'PO_TOKEN_REQUIRED_CONTEXTS': [_PoTokenContext.GVS], 'SUPPORTS_COOKIES': True, + **WEB_PO_TOKEN_POLICIES, }, # Safari UA returns pre-merged video+audio 144p/240p/360p/720p/1080p HLS formats 'web_safari': { @@ -61,8 +116,8 @@ INNERTUBE_CLIENTS = { }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 1, - 'PO_TOKEN_REQUIRED_CONTEXTS': [_PoTokenContext.GVS], 'SUPPORTS_COOKIES': True, + **WEB_PO_TOKEN_POLICIES, 'PLAYER_PARAMS': '8AEB', }, 'web_embedded': { @@ -84,7 +139,24 @@ INNERTUBE_CLIENTS = { }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 67, - 'PO_TOKEN_REQUIRED_CONTEXTS': [_PoTokenContext.GVS], + 'GVS_PO_TOKEN_POLICY': { + StreamingProtocol.HTTPS: GvsPoTokenPolicy( + required=True, + recommended=True, + not_required_for_premium=True, + not_required_with_player_token=False, + ), + StreamingProtocol.DASH: GvsPoTokenPolicy( + required=True, + recommended=True, + not_required_for_premium=True, + not_required_with_player_token=False, + ), + StreamingProtocol.HLS: GvsPoTokenPolicy( + required=False, + recommended=True, + ), + }, 'SUPPORTS_COOKIES': True, }, # This client now requires sign-in for every video @@ -96,7 +168,24 @@ INNERTUBE_CLIENTS = { }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 62, - 'PO_TOKEN_REQUIRED_CONTEXTS': [_PoTokenContext.GVS], + 'GVS_PO_TOKEN_POLICY': { + StreamingProtocol.HTTPS: GvsPoTokenPolicy( + required=True, + recommended=True, + not_required_for_premium=True, + not_required_with_player_token=False, + ), + StreamingProtocol.DASH: GvsPoTokenPolicy( + required=True, + recommended=True, + not_required_for_premium=True, + not_required_with_player_token=False, + ), + StreamingProtocol.HLS: GvsPoTokenPolicy( + required=False, + recommended=True, + ), + }, 'REQUIRE_AUTH': True, 'SUPPORTS_COOKIES': True, }, @@ -113,7 +202,24 @@ INNERTUBE_CLIENTS = { }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 3, 'REQUIRE_JS_PLAYER': False, - 'PO_TOKEN_REQUIRED_CONTEXTS': [_PoTokenContext.GVS], + 'GVS_PO_TOKEN_POLICY': { + StreamingProtocol.HTTPS: GvsPoTokenPolicy( + required=True, + recommended=True, + not_required_with_player_token=True, + ), + StreamingProtocol.DASH: GvsPoTokenPolicy( + required=True, + recommended=True, + not_required_with_player_token=True, + ), + StreamingProtocol.HLS: GvsPoTokenPolicy( + required=False, + recommended=True, + not_required_with_player_token=True, + ), + }, + 'PLAYER_PO_TOKEN_POLICY': PlayerPoTokenPolicy(required=False, recommended=True), }, # YouTube Kids videos aren't returned on this client for some reason 'android_vr': { @@ -147,7 +253,21 @@ INNERTUBE_CLIENTS = { }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 5, - 'PO_TOKEN_REQUIRED_CONTEXTS': [_PoTokenContext.GVS], + 'GVS_PO_TOKEN_POLICY': { + StreamingProtocol.HTTPS: GvsPoTokenPolicy( + required=True, + recommended=True, + not_required_with_player_token=True, + ), + # HLS Livestreams require POT 30 seconds in + # TODO: Rolling out + StreamingProtocol.HLS: GvsPoTokenPolicy( + required=False, + recommended=True, + not_required_with_player_token=True, + ), + }, + 'PLAYER_PO_TOKEN_POLICY': PlayerPoTokenPolicy(required=False, recommended=True), 'REQUIRE_JS_PLAYER': False, }, # mweb has 'ultralow' formats @@ -162,7 +282,24 @@ INNERTUBE_CLIENTS = { }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 2, - 'PO_TOKEN_REQUIRED_CONTEXTS': [_PoTokenContext.GVS], + 'GVS_PO_TOKEN_POLICY': { + StreamingProtocol.HTTPS: GvsPoTokenPolicy( + required=True, + recommended=True, + not_required_for_premium=True, + not_required_with_player_token=False, + ), + StreamingProtocol.DASH: GvsPoTokenPolicy( + required=True, + recommended=True, + not_required_for_premium=True, + not_required_with_player_token=False, + ), + StreamingProtocol.HLS: GvsPoTokenPolicy( + required=False, + recommended=True, + ), + }, 'SUPPORTS_COOKIES': True, }, 'tv': { @@ -226,7 +363,11 @@ def build_innertube_clients(): for client, ytcfg in tuple(INNERTUBE_CLIENTS.items()): ytcfg.setdefault('INNERTUBE_HOST', 'www.youtube.com') ytcfg.setdefault('REQUIRE_JS_PLAYER', True) - ytcfg.setdefault('PO_TOKEN_REQUIRED_CONTEXTS', []) + ytcfg.setdefault('GVS_PO_TOKEN_POLICY', {}) + for protocol in StreamingProtocol: + ytcfg['GVS_PO_TOKEN_POLICY'].setdefault(protocol, GvsPoTokenPolicy()) + ytcfg.setdefault('PLAYER_PO_TOKEN_POLICY', PlayerPoTokenPolicy()) + ytcfg.setdefault('SUBS_PO_TOKEN_POLICY', SubsPoTokenPolicy()) ytcfg.setdefault('REQUIRE_AUTH', False) ytcfg.setdefault('SUPPORTS_COOKIES', False) ytcfg.setdefault('PLAYER_PARAMS', None) diff --git a/yt-dlp/yt_dlp/extractor/youtube/_video.py b/yt-dlp/yt_dlp/extractor/youtube/_video.py index 208abee937..fc1f087ace 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/_video.py +++ b/yt-dlp/yt_dlp/extractor/youtube/_video.py @@ -18,6 +18,9 @@ import urllib.parse from ._base import ( INNERTUBE_CLIENTS, BadgeType, + GvsPoTokenPolicy, + PlayerPoTokenPolicy, + StreamingProtocol, YoutubeBaseInfoExtractor, _PoTokenContext, _split_innertube_client, @@ -71,9 +74,11 @@ from ...utils import ( from ...utils.networking import clean_headers, clean_proxies, select_proxy STREAMING_DATA_CLIENT_NAME = '__yt_dlp_client' -STREAMING_DATA_INITIAL_PO_TOKEN = '__yt_dlp_po_token' STREAMING_DATA_FETCH_SUBS_PO_TOKEN = '__yt_dlp_fetch_subs_po_token' +STREAMING_DATA_FETCH_GVS_PO_TOKEN = '__yt_dlp_fetch_gvs_po_token' +STREAMING_DATA_PLAYER_TOKEN_PROVIDED = '__yt_dlp_player_token_provided' STREAMING_DATA_INNERTUBE_CONTEXT = '__yt_dlp_innertube_context' +STREAMING_DATA_IS_PREMIUM_SUBSCRIBER = '__yt_dlp_is_premium_subscriber' PO_TOKEN_GUIDE_URL = 'https://github.com/yt-dlp/yt-dlp/wiki/PO-Token-Guide' @@ -253,6 +258,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor): _SUBTITLE_FORMATS = ('json3', 'srv1', 'srv2', 'srv3', 'ttml', 'srt', 'vtt') _DEFAULT_CLIENTS = ('tv', 'ios', 'web') _DEFAULT_AUTHED_CLIENTS = ('tv', 'web') + # Premium does not require POT (except for subtitles) + _DEFAULT_PREMIUM_CLIENTS = ('tv', 'web') _GEO_BYPASS = False @@ -1833,7 +1840,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if time.time() <= start_time + delay: return - _, _, prs, player_url = self._download_player_responses(url, smuggled_data, video_id, webpage_url) + _, _, _, _, prs, player_url = self._initial_extract( + url, smuggled_data, webpage_url, 'web', video_id) video_details = traverse_obj(prs, (..., 'videoDetails'), expected_type=dict) microformats = traverse_obj( prs, (..., 'microformat', 'playerMicroformatRenderer'), @@ -2891,7 +2899,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): only_once=True) continue - def fetch_po_token(self, client='web', context=_PoTokenContext.GVS, ytcfg=None, visitor_data=None, + def fetch_po_token(self, client='web', context: _PoTokenContext = _PoTokenContext.GVS, ytcfg=None, visitor_data=None, data_sync_id=None, session_index=None, player_url=None, video_id=None, webpage=None, required=False, **kwargs): """ @@ -2976,7 +2984,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor): fetch_pot_policy == 'never' or ( fetch_pot_policy == 'auto' - and _PoTokenContext(context) not in self._get_default_ytcfg(client)['PO_TOKEN_REQUIRED_CONTEXTS'] and not kwargs.get('required', False) ) ): @@ -3035,19 +3042,19 @@ class YoutubeIE(YoutubeBaseInfoExtractor): def _is_unplayable(player_response): return traverse_obj(player_response, ('playabilityStatus', 'status')) == 'UNPLAYABLE' - def _extract_player_response(self, client, video_id, master_ytcfg, player_ytcfg, player_url, initial_pr, visitor_data, data_sync_id, po_token): + def _extract_player_response(self, client, video_id, webpage_ytcfg, player_ytcfg, player_url, initial_pr, visitor_data, data_sync_id, po_token): headers = self.generate_api_headers( ytcfg=player_ytcfg, default_client=client, visitor_data=visitor_data, - session_index=self._extract_session_index(master_ytcfg, player_ytcfg), + session_index=self._extract_session_index(webpage_ytcfg, player_ytcfg), delegated_session_id=( self._parse_data_sync_id(data_sync_id)[0] - or self._extract_delegated_session_id(master_ytcfg, initial_pr, player_ytcfg) + or self._extract_delegated_session_id(webpage_ytcfg, initial_pr, player_ytcfg) ), user_session_id=( self._parse_data_sync_id(data_sync_id)[1] - or self._extract_user_session_id(master_ytcfg, initial_pr, player_ytcfg) + or self._extract_user_session_id(webpage_ytcfg, initial_pr, player_ytcfg) ), ) @@ -3063,7 +3070,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if po_token: yt_query['serviceIntegrityDimensions'] = {'poToken': po_token} - sts = self._extract_signature_timestamp(video_id, player_url, master_ytcfg, fatal=False) if player_url else None + sts = self._extract_signature_timestamp(video_id, player_url, webpage_ytcfg, fatal=False) if player_url else None yt_query.update(self._generate_player_context(sts)) return self._extract_response( item_id=video_id, ep='player', query=yt_query, @@ -3072,10 +3079,14 @@ class YoutubeIE(YoutubeBaseInfoExtractor): note='Downloading {} player API JSON'.format(client.replace('_', ' ').strip()), ) or None - def _get_requested_clients(self, url, smuggled_data): + def _get_requested_clients(self, url, smuggled_data, is_premium_subscriber): requested_clients = [] excluded_clients = [] - default_clients = self._DEFAULT_AUTHED_CLIENTS if self.is_authenticated else self._DEFAULT_CLIENTS + default_clients = ( + self._DEFAULT_PREMIUM_CLIENTS if is_premium_subscriber + else self._DEFAULT_AUTHED_CLIENTS if self.is_authenticated + else self._DEFAULT_CLIENTS + ) allowed_clients = sorted( (client for client in INNERTUBE_CLIENTS if client[:1] != '_'), key=lambda client: INNERTUBE_CLIENTS[client]['priority'], reverse=True) @@ -3117,11 +3128,12 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if (pr_id := traverse_obj(pr, ('videoDetails', 'videoId'))) != video_id: return pr_id - def _extract_player_responses(self, clients, video_id, webpage, master_ytcfg, smuggled_data): + def _extract_player_responses(self, clients, video_id, webpage, webpage_client, webpage_ytcfg, is_premium_subscriber): initial_pr = None if webpage: initial_pr = self._search_json( - self._YT_INITIAL_PLAYER_RESPONSE_RE, webpage, 'initial player response', video_id, fatal=False) + self._YT_INITIAL_PLAYER_RESPONSE_RE, webpage, + f'{webpage_client} client initial player response', video_id, fatal=False) prs = [] deprioritized_prs = [] @@ -3152,11 +3164,11 @@ class YoutubeIE(YoutubeBaseInfoExtractor): while clients: deprioritize_pr = False client, base_client, variant = _split_innertube_client(clients.pop()) - player_ytcfg = master_ytcfg if client == 'web' else {} - if 'configs' not in self._configuration_arg('player_skip') and client != 'web': + player_ytcfg = webpage_ytcfg if client == webpage_client else {} + if 'configs' not in self._configuration_arg('player_skip') and client != webpage_client: player_ytcfg = self._download_ytcfg(client, video_id) or player_ytcfg - player_url = player_url or self._extract_player_url(master_ytcfg, player_ytcfg, webpage=webpage) + player_url = player_url or self._extract_player_url(webpage_ytcfg, player_ytcfg, webpage=webpage) require_js_player = self._get_default_ytcfg(client).get('REQUIRE_JS_PLAYER') if 'js' in self._configuration_arg('player_skip'): require_js_player = False @@ -3166,10 +3178,12 @@ class YoutubeIE(YoutubeBaseInfoExtractor): player_url = self._download_player_url(video_id) tried_iframe_fallback = True - pr = initial_pr if client == 'web' else None + pr = None + if client == webpage_client and 'player_response' not in self._configuration_arg('webpage_skip'): + pr = initial_pr - visitor_data = visitor_data or self._extract_visitor_data(master_ytcfg, initial_pr, player_ytcfg) - data_sync_id = data_sync_id or self._extract_data_sync_id(master_ytcfg, initial_pr, player_ytcfg) + visitor_data = visitor_data or self._extract_visitor_data(webpage_ytcfg, initial_pr, player_ytcfg) + data_sync_id = data_sync_id or self._extract_data_sync_id(webpage_ytcfg, initial_pr, player_ytcfg) fetch_po_token_args = { 'client': client, @@ -3178,53 +3192,26 @@ class YoutubeIE(YoutubeBaseInfoExtractor): 'data_sync_id': data_sync_id if self.is_authenticated else None, 'player_url': player_url if require_js_player else None, 'webpage': webpage, - 'session_index': self._extract_session_index(master_ytcfg, player_ytcfg), + 'session_index': self._extract_session_index(webpage_ytcfg, player_ytcfg), 'ytcfg': player_ytcfg or self._get_default_ytcfg(client), } # Don't need a player PO token for WEB if using player response from webpage + player_pot_policy: PlayerPoTokenPolicy = self._get_default_ytcfg(client)['PLAYER_PO_TOKEN_POLICY'] player_po_token = None if pr else self.fetch_po_token( - context=_PoTokenContext.PLAYER, **fetch_po_token_args) + context=_PoTokenContext.PLAYER, **fetch_po_token_args, + required=player_pot_policy.required or player_pot_policy.recommended) - gvs_po_token = self.fetch_po_token( - context=_PoTokenContext.GVS, **fetch_po_token_args) + fetch_gvs_po_token_func = functools.partial( + self.fetch_po_token, context=_PoTokenContext.GVS, **fetch_po_token_args) fetch_subs_po_token_func = functools.partial( - self.fetch_po_token, - context=_PoTokenContext.SUBS, - **fetch_po_token_args, - ) - - required_pot_contexts = self._get_default_ytcfg(client)['PO_TOKEN_REQUIRED_CONTEXTS'] - - if ( - not player_po_token - and _PoTokenContext.PLAYER in required_pot_contexts - ): - # TODO: may need to skip player response request. Unsure yet.. - self.report_warning( - f'No Player PO Token provided for {client} client, ' - f'which may be required for working {client} formats. This client will be deprioritized' - f'You can manually pass a Player PO Token for this client with --extractor-args "youtube:po_token={client}.player+XXX". ' - f'For more information, refer to {PO_TOKEN_GUIDE_URL} .', only_once=True) - deprioritize_pr = True - - if ( - not gvs_po_token - and _PoTokenContext.GVS in required_pot_contexts - and 'missing_pot' in self._configuration_arg('formats') - ): - # note: warning with help message is provided later during format processing - self.report_warning( - f'No GVS PO Token provided for {client} client, ' - f'which may be required for working {client} formats. This client will be deprioritized', - only_once=True) - deprioritize_pr = True + self.fetch_po_token, context=_PoTokenContext.SUBS, **fetch_po_token_args) try: pr = pr or self._extract_player_response( client, video_id, - master_ytcfg=player_ytcfg or master_ytcfg, + webpage_ytcfg=player_ytcfg or webpage_ytcfg, player_ytcfg=player_ytcfg, player_url=player_url, initial_pr=initial_pr, @@ -3242,12 +3229,16 @@ class YoutubeIE(YoutubeBaseInfoExtractor): innertube_context = traverse_obj(player_ytcfg or self._get_default_ytcfg(client), 'INNERTUBE_CONTEXT') sd = pr.setdefault('streamingData', {}) sd[STREAMING_DATA_CLIENT_NAME] = client - sd[STREAMING_DATA_INITIAL_PO_TOKEN] = gvs_po_token + sd[STREAMING_DATA_FETCH_GVS_PO_TOKEN] = fetch_gvs_po_token_func + sd[STREAMING_DATA_PLAYER_TOKEN_PROVIDED] = bool(player_po_token) sd[STREAMING_DATA_INNERTUBE_CONTEXT] = innertube_context sd[STREAMING_DATA_FETCH_SUBS_PO_TOKEN] = fetch_subs_po_token_func + sd[STREAMING_DATA_IS_PREMIUM_SUBSCRIBER] = is_premium_subscriber for f in traverse_obj(sd, (('formats', 'adaptiveFormats'), ..., {dict})): f[STREAMING_DATA_CLIENT_NAME] = client - f[STREAMING_DATA_INITIAL_PO_TOKEN] = gvs_po_token + f[STREAMING_DATA_FETCH_GVS_PO_TOKEN] = fetch_gvs_po_token_func + f[STREAMING_DATA_IS_PREMIUM_SUBSCRIBER] = is_premium_subscriber + f[STREAMING_DATA_PLAYER_TOKEN_PROVIDED] = bool(player_po_token) if deprioritize_pr: deprioritized_prs.append(pr) else: @@ -3357,6 +3348,15 @@ class YoutubeIE(YoutubeBaseInfoExtractor): }), } for range_start in range(0, f['filesize'], CHUNK_SIZE)) + def gvs_pot_required(policy, is_premium_subscriber, has_player_token): + return ( + policy.required + and not (policy.not_required_with_player_token and has_player_token) + and not (policy.not_required_for_premium and is_premium_subscriber)) + + # save pots per client to avoid fetching again + gvs_pots = {} + for fmt in streaming_formats: client_name = fmt[STREAMING_DATA_CLIENT_NAME] if fmt.get('targetDurationSec'): @@ -3416,7 +3416,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): encrypted_sig = try_get(sc, lambda x: x['s'][0]) if not all((sc, fmt_url, player_url, encrypted_sig)): msg = f'Some {client_name} client https formats have been skipped as they are missing a url. ' - if client_name == 'web': + if client_name in ('web', 'web_safari'): msg += 'YouTube is forcing SABR streaming for this client. ' else: msg += ( @@ -3476,18 +3476,25 @@ class YoutubeIE(YoutubeBaseInfoExtractor): self.report_warning( 'Some formats are possibly damaged. They will be deprioritized', video_id, only_once=True) - po_token = fmt.get(STREAMING_DATA_INITIAL_PO_TOKEN) + fetch_po_token_func = fmt[STREAMING_DATA_FETCH_GVS_PO_TOKEN] + pot_policy: GvsPoTokenPolicy = self._get_default_ytcfg(client_name)['GVS_PO_TOKEN_POLICY'][StreamingProtocol.HTTPS] + + require_po_token = ( + itag not in ['18'] + and gvs_pot_required( + pot_policy, fmt[STREAMING_DATA_IS_PREMIUM_SUBSCRIBER], + fmt[STREAMING_DATA_PLAYER_TOKEN_PROVIDED])) + + po_token = ( + gvs_pots.get(client_name) + or fetch_po_token_func(required=require_po_token or pot_policy.recommended)) if po_token: fmt_url = update_url_query(fmt_url, {'pot': po_token}) + if client_name not in gvs_pots: + gvs_pots[client_name] = po_token - # Clients that require PO Token return videoplayback URLs that may return 403 - require_po_token = ( - not po_token - and _PoTokenContext.GVS in self._get_default_ytcfg(client_name)['PO_TOKEN_REQUIRED_CONTEXTS'] - and itag not in ['18']) # these formats do not require PO Token - - if require_po_token and 'missing_pot' not in self._configuration_arg('formats'): + if not po_token and require_po_token and 'missing_pot' not in self._configuration_arg('formats'): self._report_pot_format_skipped(video_id, client_name, 'https') continue @@ -3502,7 +3509,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): name, fmt.get('isDrc') and 'DRC', try_get(fmt, lambda x: x['projectionType'].replace('RECTANGULAR', '').lower()), try_get(fmt, lambda x: x['spatialAudioType'].replace('SPATIAL_AUDIO_TYPE_', '').lower()), - is_damaged and 'DAMAGED', require_po_token and 'MISSING POT', + is_damaged and 'DAMAGED', require_po_token and not po_token and 'MISSING POT', (self.get_param('verbose') or all_formats) and short_client_name(client_name), delim=', '), # Format 22 is likely to be damaged. See https://github.com/yt-dlp/yt-dlp/issues/3372 @@ -3565,7 +3572,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): elif skip_bad_formats and live_status == 'is_live' and needs_live_processing != 'is_live': skip_manifests.add('dash') - def process_manifest_format(f, proto, client_name, itag, po_token): + def process_manifest_format(f, proto, client_name, itag, missing_pot): key = (proto, f.get('language')) if not all_formats and key in itags[itag]: return False @@ -3573,20 +3580,11 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if f.get('source_preference') is None: f['source_preference'] = -1 - # Clients that require PO Token return videoplayback URLs that may return 403 - # hls does not currently require PO Token - if ( - not po_token - and _PoTokenContext.GVS in self._get_default_ytcfg(client_name)['PO_TOKEN_REQUIRED_CONTEXTS'] - and proto != 'hls' - ): - if 'missing_pot' not in self._configuration_arg('formats'): - self._report_pot_format_skipped(video_id, client_name, proto) - return False + if missing_pot: f['format_note'] = join_nonempty(f.get('format_note'), 'MISSING POT', delim=' ') f['source_preference'] -= 20 - # XXX: Check if IOS HLS formats are affected by player PO token enforcement; temporary + # XXX: Check if IOS HLS formats are affected by PO token enforcement; temporary # See https://github.com/yt-dlp/yt-dlp/issues/13511 if proto == 'hls' and client_name == 'ios': f['__needs_testing'] = True @@ -3625,39 +3623,62 @@ class YoutubeIE(YoutubeBaseInfoExtractor): subtitles = {} for sd in streaming_data: client_name = sd[STREAMING_DATA_CLIENT_NAME] - po_token = sd.get(STREAMING_DATA_INITIAL_PO_TOKEN) + fetch_pot_func = sd[STREAMING_DATA_FETCH_GVS_PO_TOKEN] + is_premium_subscriber = sd[STREAMING_DATA_IS_PREMIUM_SUBSCRIBER] + has_player_token = sd[STREAMING_DATA_PLAYER_TOKEN_PROVIDED] + hls_manifest_url = 'hls' not in skip_manifests and sd.get('hlsManifestUrl') if hls_manifest_url: + pot_policy: GvsPoTokenPolicy = self._get_default_ytcfg( + client_name)['GVS_PO_TOKEN_POLICY'][StreamingProtocol.HLS] + require_po_token = gvs_pot_required(pot_policy, is_premium_subscriber, has_player_token) + po_token = gvs_pots.get(client_name, fetch_pot_func(required=require_po_token or pot_policy.recommended)) if po_token: hls_manifest_url = hls_manifest_url.rstrip('/') + f'/pot/{po_token}' - fmts, subs = self._extract_m3u8_formats_and_subtitles( - hls_manifest_url, video_id, 'mp4', fatal=False, live=live_status == 'is_live') - for sub in traverse_obj(subs, (..., ..., {dict})): - # HLS subs (m3u8) do not need a PO token; save client name for debugging - sub[STREAMING_DATA_CLIENT_NAME] = client_name - subtitles = self._merge_subtitles(subs, subtitles) - for f in fmts: - if process_manifest_format(f, 'hls', client_name, self._search_regex( - r'/itag/(\d+)', f['url'], 'itag', default=None), po_token): - yield f + if client_name not in gvs_pots: + gvs_pots[client_name] = po_token + if require_po_token and not po_token and 'missing_pot' not in self._configuration_arg('formats'): + self._report_pot_format_skipped(video_id, client_name, 'hls') + else: + fmts, subs = self._extract_m3u8_formats_and_subtitles( + hls_manifest_url, video_id, 'mp4', fatal=False, live=live_status == 'is_live') + for sub in traverse_obj(subs, (..., ..., {dict})): + # TODO: If HLS video requires a PO Token, do the subs also require pot? + # Save client name for debugging + sub[STREAMING_DATA_CLIENT_NAME] = client_name + subtitles = self._merge_subtitles(subs, subtitles) + for f in fmts: + if process_manifest_format(f, 'hls', client_name, self._search_regex( + r'/itag/(\d+)', f['url'], 'itag', default=None), require_po_token and not po_token): + yield f dash_manifest_url = 'dash' not in skip_manifests and sd.get('dashManifestUrl') if dash_manifest_url: + pot_policy: GvsPoTokenPolicy = self._get_default_ytcfg( + client_name)['GVS_PO_TOKEN_POLICY'][StreamingProtocol.DASH] + require_po_token = gvs_pot_required(pot_policy, is_premium_subscriber, has_player_token) + po_token = gvs_pots.get(client_name, fetch_pot_func(required=require_po_token or pot_policy.recommended)) if po_token: dash_manifest_url = dash_manifest_url.rstrip('/') + f'/pot/{po_token}' - formats, subs = self._extract_mpd_formats_and_subtitles(dash_manifest_url, video_id, fatal=False) - for sub in traverse_obj(subs, (..., ..., {dict})): - # TODO: Investigate if DASH subs ever need a PO token; save client name for debugging - sub[STREAMING_DATA_CLIENT_NAME] = client_name - subtitles = self._merge_subtitles(subs, subtitles) # Prioritize HLS subs over DASH - for f in formats: - if process_manifest_format(f, 'dash', client_name, f['format_id'], po_token): - f['filesize'] = int_or_none(self._search_regex( - r'/clen/(\d+)', f.get('fragment_base_url') or f['url'], 'file size', default=None)) - if needs_live_processing: - f['is_from_start'] = True + if client_name not in gvs_pots: + gvs_pots[client_name] = po_token + if require_po_token and not po_token and 'missing_pot' not in self._configuration_arg('formats'): + self._report_pot_format_skipped(video_id, client_name, 'dash') + else: + formats, subs = self._extract_mpd_formats_and_subtitles(dash_manifest_url, video_id, fatal=False) + for sub in traverse_obj(subs, (..., ..., {dict})): + # TODO: If DASH video requires a PO Token, do the subs also require pot? + # Save client name for debugging + sub[STREAMING_DATA_CLIENT_NAME] = client_name + subtitles = self._merge_subtitles(subs, subtitles) # Prioritize HLS subs over DASH + for f in formats: + if process_manifest_format(f, 'dash', client_name, f['format_id'], require_po_token and not po_token): + f['filesize'] = int_or_none(self._search_regex( + r'/clen/(\d+)', f.get('fragment_base_url') or f['url'], 'file size', default=None)) + if needs_live_processing: + f['is_from_start'] = True - yield f + yield f yield subtitles def _extract_storyboard(self, player_responses, duration): @@ -3698,22 +3719,22 @@ class YoutubeIE(YoutubeBaseInfoExtractor): } for j in range(math.ceil(fragment_count))], } - def _download_player_responses(self, url, smuggled_data, video_id, webpage_url): + def _download_initial_webpage(self, webpage_url, webpage_client, video_id): webpage = None - if 'webpage' not in self._configuration_arg('player_skip'): + if webpage_url and 'webpage' not in self._configuration_arg('player_skip'): query = {'bpctr': '9999999999', 'has_verified': '1'} - pp = self._configuration_arg('player_params', [None], casesense=True)[0] + pp = ( + self._configuration_arg('player_params', [None], casesense=True)[0] + or traverse_obj(INNERTUBE_CLIENTS, (webpage_client, 'PLAYER_PARAMS', {str})) + ) if pp: query['pp'] = pp - webpage = self._download_webpage_with_retries(webpage_url, video_id, query=query) - - master_ytcfg = self.extract_ytcfg(video_id, webpage) or self._get_default_ytcfg() - - player_responses, player_url = self._extract_player_responses( - self._get_requested_clients(url, smuggled_data), - video_id, webpage, master_ytcfg, smuggled_data) - - return webpage, master_ytcfg, player_responses, player_url + webpage = self._download_webpage_with_retries( + webpage_url, video_id, query=query, + headers=traverse_obj(self._get_default_ytcfg(webpage_client), { + 'User-Agent': ('INNERTUBE_CONTEXT', 'client', 'userAgent', {str}), + })) + return webpage def _list_formats(self, video_id, microformats, video_details, player_responses, player_url, duration=None): live_broadcast_details = traverse_obj(microformats, (..., 'liveBroadcastDetails')) @@ -3738,14 +3759,60 @@ class YoutubeIE(YoutubeBaseInfoExtractor): return live_broadcast_details, live_status, streaming_data, formats, subtitles + def _download_initial_data(self, video_id, webpage, webpage_client, webpage_ytcfg): + initial_data = None + if webpage and 'initial_data' not in self._configuration_arg('webpage_skip'): + initial_data = self.extract_yt_initial_data(video_id, webpage, fatal=False) + if not traverse_obj(initial_data, 'contents'): + self.report_warning('Incomplete data received in embedded initial data; re-fetching using API.') + initial_data = None + if not initial_data and 'initial_data' not in self._configuration_arg('player_skip'): + query = {'videoId': video_id} + query.update(self._get_checkok_params()) + initial_data = self._extract_response( + item_id=video_id, ep='next', fatal=False, + ytcfg=webpage_ytcfg, query=query, check_get_keys='contents', + note='Downloading initial data API JSON', default_client=webpage_client) + return initial_data + + def _is_premium_subscriber(self, initial_data): + if not self.is_authenticated or not initial_data: + return False + + tlr = traverse_obj( + initial_data, ('topbar', 'desktopTopbarRenderer', 'logo', 'topbarLogoRenderer')) + return ( + traverse_obj(tlr, ('iconImage', 'iconType')) == 'YOUTUBE_PREMIUM_LOGO' + or 'premium' in (self._get_text(tlr, 'tooltipText') or '').lower() + ) + + def _initial_extract(self, url, smuggled_data, webpage_url, webpage_client, video_id): + # This function is also used by live-from-start refresh + webpage = self._download_initial_webpage(webpage_url, webpage_client, video_id) + webpage_ytcfg = self.extract_ytcfg(video_id, webpage) or self._get_default_ytcfg(webpage_client) + + initial_data = self._download_initial_data(video_id, webpage, webpage_client, webpage_ytcfg) + + is_premium_subscriber = self._is_premium_subscriber(initial_data) + if is_premium_subscriber: + self.write_debug('Detected YouTube Premium subscription') + + player_responses, player_url = self._extract_player_responses( + self._get_requested_clients(url, smuggled_data, is_premium_subscriber), + video_id, webpage, webpage_client, webpage_ytcfg, is_premium_subscriber) + + return webpage, webpage_ytcfg, initial_data, is_premium_subscriber, player_responses, player_url + def _real_extract(self, url): url, smuggled_data = unsmuggle_url(url, {}) video_id = self._match_id(url) base_url = self.http_scheme() + '//www.youtube.com/' webpage_url = base_url + 'watch?v=' + video_id + webpage_client = 'web' - webpage, master_ytcfg, player_responses, player_url = self._download_player_responses(url, smuggled_data, video_id, webpage_url) + webpage, webpage_ytcfg, initial_data, is_premium_subscriber, player_responses, player_url = self._initial_extract( + url, smuggled_data, webpage_url, webpage_client, video_id) playability_statuses = traverse_obj( player_responses, (..., 'playabilityStatus'), expected_type=dict) @@ -4020,7 +4087,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): pctr = pr['captions']['playerCaptionsTracklistRenderer'] client_name = pr['streamingData'][STREAMING_DATA_CLIENT_NAME] innertube_client_name = pr['streamingData'][STREAMING_DATA_INNERTUBE_CONTEXT]['client']['clientName'] - required_contexts = self._get_default_ytcfg(client_name)['PO_TOKEN_REQUIRED_CONTEXTS'] + pot_policy: GvsPoTokenPolicy = self._get_default_ytcfg(client_name)['SUBS_PO_TOKEN_POLICY'] fetch_subs_po_token_func = pr['streamingData'][STREAMING_DATA_FETCH_SUBS_PO_TOKEN] pot_params = {} @@ -4033,11 +4100,11 @@ class YoutubeIE(YoutubeBaseInfoExtractor): requires_pot = ( # We can detect the experiment for now any(e in traverse_obj(qs, ('exp', ...)) for e in ('xpe', 'xpv')) - or _PoTokenContext.SUBS in required_contexts) + or (pot_policy.required and not (pot_policy.not_required_for_premium and is_premium_subscriber))) if not already_fetched_pot: already_fetched_pot = True - if subs_po_token := fetch_subs_po_token_func(required=requires_pot): + if subs_po_token := fetch_subs_po_token_func(required=requires_pot or pot_policy.recommended): pot_params.update({ 'pot': subs_po_token, 'potc': '1', @@ -4140,21 +4207,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor): 'release_year': int_or_none(release_year), }) - initial_data = None - if webpage: - initial_data = self.extract_yt_initial_data(video_id, webpage, fatal=False) - if not traverse_obj(initial_data, 'contents'): - self.report_warning('Incomplete data received in embedded initial data; re-fetching using API.') - initial_data = None - if not initial_data and 'initial_data' not in self._configuration_arg('player_skip'): - query = {'videoId': video_id} - query.update(self._get_checkok_params()) - initial_data = self._extract_response( - item_id=video_id, ep='next', fatal=False, - ytcfg=master_ytcfg, query=query, check_get_keys='contents', - headers=self.generate_api_headers(ytcfg=master_ytcfg), - note='Downloading initial data API JSON') - COMMENTS_SECTION_IDS = ('comment-item-section', 'engagement-panel-comments-section') info['comment_count'] = traverse_obj(initial_data, ( 'contents', 'twoColumnWatchNextResults', 'results', 'results', 'contents', ..., 'itemSectionRenderer', @@ -4353,7 +4405,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): self._has_badge(badges, BadgeType.AVAILABILITY_UNLISTED) or get_first(microformats, 'isUnlisted', expected_type=bool)))) - info['__post_extractor'] = self.extract_comments(master_ytcfg, video_id, contents, webpage) + info['__post_extractor'] = self.extract_comments(webpage_ytcfg, video_id, contents, webpage) self.mark_watched(video_id, player_responses)