diff --git a/.github/update.log b/.github/update.log index fb88b9103d..f00c0b9b77 100644 --- a/.github/update.log +++ b/.github/update.log @@ -687,3 +687,4 @@ Update On Tue Jun 25 20:31:14 CEST 2024 Update On Wed Jun 26 20:32:07 CEST 2024 Update On Thu Jun 27 20:29:48 CEST 2024 Update On Fri Jun 28 20:30:26 CEST 2024 +Update On Sat Jun 29 20:29:36 CEST 2024 diff --git a/clash-nyanpasu/backend/Cargo.lock b/clash-nyanpasu/backend/Cargo.lock index a17885b62e..07c0c560f9 100644 --- a/clash-nyanpasu/backend/Cargo.lock +++ b/clash-nyanpasu/backend/Cargo.lock @@ -819,9 +819,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.7" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" dependencies = [ "clap_builder", "clap_derive", @@ -829,9 +829,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" dependencies = [ "anstream", "anstyle", @@ -841,9 +841,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.5" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -3152,7 +3152,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.5", + "windows-targets 0.48.5", ] [[package]] diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index 7d49d26715..1d4f0f89ec 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -2,7 +2,7 @@ "manifest_version": 1, "latest": { "mihomo": "v1.18.5", - "mihomo_alpha": "alpha-f45ccc0", + "mihomo_alpha": "alpha-0e22876", "clash_rs": "v0.1.18", "clash_premium": "2023-09-05-gdcc8d87" }, @@ -36,5 +36,5 @@ "darwin-x64": "clash-darwin-amd64-n{}.gz" } }, - "updated_at": "2024-06-27T22:19:55.721Z" + "updated_at": "2024-06-28T22:20:05.617Z" } diff --git a/clash-nyanpasu/package.json b/clash-nyanpasu/package.json index 7656877cbc..9fb7a0fa88 100644 --- a/clash-nyanpasu/package.json +++ b/clash-nyanpasu/package.json @@ -106,7 +106,7 @@ "stylelint-order": "6.0.4", "stylelint-scss": "6.3.2", "tailwindcss": "3.4.4", - "tsx": "4.15.8", + "tsx": "4.16.0", "typescript": "5.5.2" }, "packageManager": "pnpm@9.4.0", diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index 7ce2150835..39a27cf032 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -134,8 +134,8 @@ importers: specifier: 3.4.4 version: 3.4.4 tsx: - specifier: 4.15.8 - version: 4.15.8 + specifier: 4.16.0 + version: 4.16.0 typescript: specifier: 5.5.2 version: 5.5.2 @@ -757,6 +757,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.19.12': resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} engines: {node: '>=12'} @@ -769,6 +775,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.19.12': resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} engines: {node: '>=12'} @@ -781,6 +793,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.19.12': resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} engines: {node: '>=12'} @@ -793,6 +811,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.19.12': resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} engines: {node: '>=12'} @@ -805,6 +829,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.19.12': resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} engines: {node: '>=12'} @@ -817,6 +847,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.19.12': resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} engines: {node: '>=12'} @@ -829,6 +865,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.19.12': resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} engines: {node: '>=12'} @@ -841,6 +883,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.19.12': resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} engines: {node: '>=12'} @@ -853,6 +901,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.19.12': resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} engines: {node: '>=12'} @@ -865,6 +919,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.19.12': resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} engines: {node: '>=12'} @@ -877,6 +937,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.19.12': resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} engines: {node: '>=12'} @@ -889,6 +955,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.19.12': resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} engines: {node: '>=12'} @@ -901,6 +973,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.19.12': resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} engines: {node: '>=12'} @@ -913,6 +991,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.19.12': resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} engines: {node: '>=12'} @@ -925,6 +1009,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.19.12': resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} engines: {node: '>=12'} @@ -937,6 +1027,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.19.12': resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} engines: {node: '>=12'} @@ -949,6 +1045,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-x64@0.19.12': resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} @@ -961,6 +1063,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-x64@0.19.12': resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} @@ -973,6 +1081,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.19.12': resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} @@ -985,6 +1099,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.19.12': resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} engines: {node: '>=12'} @@ -997,6 +1117,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.19.12': resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} engines: {node: '>=12'} @@ -1009,6 +1135,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.19.12': resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} engines: {node: '>=12'} @@ -1021,6 +1153,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2951,6 +3089,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} @@ -5356,8 +5499,8 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - tsx@4.15.8: - resolution: {integrity: sha512-B8dMlbbkZPW0GQ7wafyy2TGXyoGYW0IURfWkM1h/WzgG5lxxRoeDU2VbMURmmjwGaCsoKROVTLmQQPe/s2TnLw==} + tsx@4.16.0: + resolution: {integrity: sha512-MPgN+CuY+4iKxGoJNPv+1pyo5YWZAQ5XfsyobUG+zoKG7IkvCPLZDEyoIb8yLS2FcWci1nlxAqmvPlFWD5AFiQ==} engines: {node: '>=18.0.0'} hasBin: true @@ -6155,138 +6298,207 @@ snapshots: '@esbuild/aix-ppc64@0.21.4': optional: true + '@esbuild/aix-ppc64@0.21.5': + optional: true + '@esbuild/android-arm64@0.19.12': optional: true '@esbuild/android-arm64@0.21.4': optional: true + '@esbuild/android-arm64@0.21.5': + optional: true + '@esbuild/android-arm@0.19.12': optional: true '@esbuild/android-arm@0.21.4': optional: true + '@esbuild/android-arm@0.21.5': + optional: true + '@esbuild/android-x64@0.19.12': optional: true '@esbuild/android-x64@0.21.4': optional: true + '@esbuild/android-x64@0.21.5': + optional: true + '@esbuild/darwin-arm64@0.19.12': optional: true '@esbuild/darwin-arm64@0.21.4': optional: true + '@esbuild/darwin-arm64@0.21.5': + optional: true + '@esbuild/darwin-x64@0.19.12': optional: true '@esbuild/darwin-x64@0.21.4': optional: true + '@esbuild/darwin-x64@0.21.5': + optional: true + '@esbuild/freebsd-arm64@0.19.12': optional: true '@esbuild/freebsd-arm64@0.21.4': optional: true + '@esbuild/freebsd-arm64@0.21.5': + optional: true + '@esbuild/freebsd-x64@0.19.12': optional: true '@esbuild/freebsd-x64@0.21.4': optional: true + '@esbuild/freebsd-x64@0.21.5': + optional: true + '@esbuild/linux-arm64@0.19.12': optional: true '@esbuild/linux-arm64@0.21.4': optional: true + '@esbuild/linux-arm64@0.21.5': + optional: true + '@esbuild/linux-arm@0.19.12': optional: true '@esbuild/linux-arm@0.21.4': optional: true + '@esbuild/linux-arm@0.21.5': + optional: true + '@esbuild/linux-ia32@0.19.12': optional: true '@esbuild/linux-ia32@0.21.4': optional: true + '@esbuild/linux-ia32@0.21.5': + optional: true + '@esbuild/linux-loong64@0.19.12': optional: true '@esbuild/linux-loong64@0.21.4': optional: true + '@esbuild/linux-loong64@0.21.5': + optional: true + '@esbuild/linux-mips64el@0.19.12': optional: true '@esbuild/linux-mips64el@0.21.4': optional: true + '@esbuild/linux-mips64el@0.21.5': + optional: true + '@esbuild/linux-ppc64@0.19.12': optional: true '@esbuild/linux-ppc64@0.21.4': optional: true + '@esbuild/linux-ppc64@0.21.5': + optional: true + '@esbuild/linux-riscv64@0.19.12': optional: true '@esbuild/linux-riscv64@0.21.4': optional: true + '@esbuild/linux-riscv64@0.21.5': + optional: true + '@esbuild/linux-s390x@0.19.12': optional: true '@esbuild/linux-s390x@0.21.4': optional: true + '@esbuild/linux-s390x@0.21.5': + optional: true + '@esbuild/linux-x64@0.19.12': optional: true '@esbuild/linux-x64@0.21.4': optional: true + '@esbuild/linux-x64@0.21.5': + optional: true + '@esbuild/netbsd-x64@0.19.12': optional: true '@esbuild/netbsd-x64@0.21.4': optional: true + '@esbuild/netbsd-x64@0.21.5': + optional: true + '@esbuild/openbsd-x64@0.19.12': optional: true '@esbuild/openbsd-x64@0.21.4': optional: true + '@esbuild/openbsd-x64@0.21.5': + optional: true + '@esbuild/sunos-x64@0.19.12': optional: true '@esbuild/sunos-x64@0.21.4': optional: true + '@esbuild/sunos-x64@0.21.5': + optional: true + '@esbuild/win32-arm64@0.19.12': optional: true '@esbuild/win32-arm64@0.21.4': optional: true + '@esbuild/win32-arm64@0.21.5': + optional: true + '@esbuild/win32-ia32@0.19.12': optional: true '@esbuild/win32-ia32@0.21.4': optional: true + '@esbuild/win32-ia32@0.21.5': + optional: true + '@esbuild/win32-x64@0.19.12': optional: true '@esbuild/win32-x64@0.21.4': optional: true + '@esbuild/win32-x64@0.21.5': + optional: true + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': dependencies: eslint: 8.57.0 @@ -8428,6 +8640,32 @@ snapshots: '@esbuild/win32-ia32': 0.21.4 '@esbuild/win32-x64': 0.21.4 + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + escalade@3.1.2: {} escape-string-regexp@1.0.5: {} @@ -11111,9 +11349,9 @@ snapshots: tslib@2.6.2: {} - tsx@4.15.8: + tsx@4.16.0: dependencies: - esbuild: 0.21.4 + esbuild: 0.21.5 get-tsconfig: 4.7.5 optionalDependencies: fsevents: 2.3.3 diff --git a/clash-verge-rev/src-tauri/Cargo.lock b/clash-verge-rev/src-tauri/Cargo.lock index 3f78dad54b..24f9943841 100644 --- a/clash-verge-rev/src-tauri/Cargo.lock +++ b/clash-verge-rev/src-tauri/Cargo.lock @@ -4505,7 +4505,6 @@ dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", - "futures-channel", "futures-core", "futures-util", "h2 0.4.5", diff --git a/clash-verge-rev/src-tauri/Cargo.toml b/clash-verge-rev/src-tauri/Cargo.toml index 64a201ff2f..2f00f8d6e0 100644 --- a/clash-verge-rev/src-tauri/Cargo.toml +++ b/clash-verge-rev/src-tauri/Cargo.toml @@ -35,7 +35,7 @@ percent-encoding = "2.3.1" window-shadows = { version = "0.2" } tokio = { version = "1", features = ["full"] } serde = { version = "1.0", features = ["derive"] } -reqwest = { version = "0.12", features = ["json", "rustls-tls", "blocking"] } +reqwest = { version = "0.12", features = ["json", "rustls-tls"] } sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" } tauri = { git="https://github.com/tauri-apps/tauri",branch = "1.x", features = [ "fs-read-file", "fs-exists", "path-all", "protocol-asset", "dialog-open", "notification-all", "icon-png", "icon-ico", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all", "devtools"] } [target.'cfg(windows)'.dependencies] diff --git a/clash-verge-rev/src-tauri/src/config/clash.rs b/clash-verge-rev/src-tauri/src/config/clash.rs index 7f782a3561..e1d4aa3ed5 100644 --- a/clash-verge-rev/src-tauri/src/config/clash.rs +++ b/clash-verge-rev/src-tauri/src/config/clash.rs @@ -13,7 +13,7 @@ pub struct IClashTemp(pub Mapping); impl IClashTemp { pub fn new() -> Self { let template = Self::template(); - match dirs::clash_path().and_then(|path| help::read_merge_mapping(&path)) { + match dirs::clash_path().and_then(|path| help::read_mapping(&path)) { Ok(mut map) => { template.0.keys().for_each(|key| { if !map.contains_key(key) { diff --git a/clash-verge-rev/src-tauri/src/config/config.rs b/clash-verge-rev/src-tauri/src/config/config.rs index 22436bea83..8664596419 100644 --- a/clash-verge-rev/src-tauri/src/config/config.rs +++ b/clash-verge-rev/src-tauri/src/config/config.rs @@ -46,8 +46,8 @@ impl Config { } /// 初始化订阅 - pub fn init_config() -> Result<()> { - crate::log_err!(Self::generate()); + pub async fn init_config() -> Result<()> { + crate::log_err!(Self::generate().await); if let Err(err) = Self::generate_file(ConfigType::Run) { log::error!(target: "app", "{err}"); @@ -83,8 +83,8 @@ impl Config { } /// 生成订阅存好 - pub fn generate() -> Result<()> { - let (config, exists_keys, logs) = enhance::enhance(); + pub async fn generate() -> Result<()> { + let (config, exists_keys, logs) = enhance::enhance().await; *Config::runtime().draft() = IRuntime { config: Some(config), diff --git a/clash-verge-rev/src-tauri/src/config/prfitem.rs b/clash-verge-rev/src-tauri/src/config/prfitem.rs index d2a7955a91..32d846f77f 100644 --- a/clash-verge-rev/src-tauri/src/config/prfitem.rs +++ b/clash-verge-rev/src-tauri/src/config/prfitem.rs @@ -94,6 +94,16 @@ pub struct PrfOption { /// default is `false` #[serde(skip_serializing_if = "Option::is_none")] pub danger_accept_invalid_certs: Option, + + pub merge: Option, + + pub script: Option, + + pub rules: Option, + + pub proxies: Option, + + pub groups: Option, } impl PrfOption { @@ -107,6 +117,11 @@ impl PrfOption { .danger_accept_invalid_certs .or(a.danger_accept_invalid_certs); a.update_interval = b.update_interval.or(a.update_interval); + a.merge = b.merge.or(a.merge); + a.script = b.script.or(a.script); + a.rules = b.rules.or(a.rules); + a.proxies = b.proxies.or(a.proxies); + a.groups = b.groups.or(a.groups); Some(a) } t => t.0.or(t.1), @@ -137,16 +152,6 @@ impl PrfItem { let desc = item.desc.unwrap_or("".into()); PrfItem::from_local(name, desc, file_data, item.option) } - "merge" => { - let name = item.name.unwrap_or("Merge".into()); - let desc = item.desc.unwrap_or("".into()); - PrfItem::from_merge(name, desc) - } - "script" => { - let name = item.name.unwrap_or("Script".into()); - let desc = item.desc.unwrap_or("".into()); - PrfItem::from_script(name, desc) - } typ => bail!("invalid profile item type \"{typ}\""), } } @@ -159,9 +164,43 @@ impl PrfItem { file_data: Option, option: Option, ) -> Result { - let uid = help::get_uid("l"); + let uid = help::get_uid("L"); let file = format!("{uid}.yaml"); + let opt_ref = option.as_ref(); + let update_interval = opt_ref.and_then(|o| o.update_interval); + let mut merge = opt_ref.and_then(|o| o.merge.clone()); + let mut script = opt_ref.and_then(|o| o.script.clone()); + let mut rules = opt_ref.and_then(|o| o.rules.clone()); + let mut proxies = opt_ref.and_then(|o| o.proxies.clone()); + let mut groups = opt_ref.and_then(|o| o.groups.clone()); + if merge.is_none() { + let merge_item = PrfItem::from_merge()?; + Config::profiles().data().append_item(merge_item.clone())?; + merge = merge_item.uid; + } + if script.is_none() { + let script_item = PrfItem::from_script()?; + Config::profiles().data().append_item(script_item.clone())?; + script = script_item.uid; + } + if rules.is_none() { + let rules_item = PrfItem::from_rules()?; + Config::profiles().data().append_item(rules_item.clone())?; + rules = rules_item.uid; + } + if proxies.is_none() { + let proxies_item = PrfItem::from_proxies()?; + Config::profiles() + .data() + .append_item(proxies_item.clone())?; + proxies = proxies_item.uid; + } + if groups.is_none() { + let groups_item = PrfItem::from_groups()?; + Config::profiles().data().append_item(groups_item.clone())?; + groups = groups_item.uid; + } Ok(PrfItem { uid: Some(uid), itype: Some("local".into()), @@ -172,7 +211,12 @@ impl PrfItem { selected: None, extra: None, option: Some(PrfOption { - update_interval: option.unwrap_or_default().update_interval, + update_interval, + merge, + script, + rules, + proxies, + groups, ..PrfOption::default() }), home: None, @@ -196,9 +240,40 @@ impl PrfItem { opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false)); let user_agent = opt_ref.and_then(|o| o.user_agent.clone()); let update_interval = opt_ref.and_then(|o| o.update_interval); - + let mut merge = opt_ref.and_then(|o| o.merge.clone()); + let mut script = opt_ref.and_then(|o| o.script.clone()); + let mut rules = opt_ref.and_then(|o| o.rules.clone()); + let mut proxies = opt_ref.and_then(|o| o.proxies.clone()); + let mut groups = opt_ref.and_then(|o| o.groups.clone()); let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy(); + if merge.is_none() { + let merge_item = PrfItem::from_merge()?; + Config::profiles().data().append_item(merge_item.clone())?; + merge = merge_item.uid; + } + if script.is_none() { + let script_item = PrfItem::from_script()?; + Config::profiles().data().append_item(script_item.clone())?; + script = script_item.uid; + } + if rules.is_none() { + let rules_item = PrfItem::from_rules()?; + Config::profiles().data().append_item(rules_item.clone())?; + rules = rules_item.uid; + } + if proxies.is_none() { + let proxies_item = PrfItem::from_proxies()?; + Config::profiles() + .data() + .append_item(proxies_item.clone())?; + proxies = proxies_item.uid; + } + if groups.is_none() { + let groups_item = PrfItem::from_groups()?; + Config::profiles().data().append_item(groups_item.clone())?; + groups = groups_item.uid; + } // 使用软件自己的代理 if self_proxy { let port = Config::verge() @@ -290,17 +365,11 @@ impl PrfItem { crate::utils::help::get_last_part_and_decode(url).unwrap_or("Remote File".into()), ), }; - let option = match update_interval { - Some(val) => Some(PrfOption { - update_interval: Some(val), - ..PrfOption::default() - }), + let update_interval = match update_interval { + Some(val) => Some(val), None => match header.get("profile-update-interval") { Some(value) => match value.to_str().unwrap_or("").parse::() { - Ok(val) => Some(PrfOption { - update_interval: Some(val * 60), // hour -> min - ..PrfOption::default() - }), + Ok(val) => Some(val * 60), // hour -> min Err(_) => None, }, None => None, @@ -315,7 +384,7 @@ impl PrfItem { None => None, }; - let uid = help::get_uid("r"); + let uid = help::get_uid("R"); let file = format!("{uid}.yaml"); let name = name.unwrap_or(filename.unwrap_or("Remote File".into())); let data = resp.text_with_charset("utf-8").await?; @@ -340,7 +409,15 @@ impl PrfItem { url: Some(url.into()), selected: None, extra, - option, + option: Some(PrfOption { + update_interval, + merge, + script, + rules, + proxies, + groups, + ..PrfOption::default() + }), home, updated: Some(chrono::Local::now().timestamp() as usize), file_data: Some(data.into()), @@ -349,15 +426,15 @@ impl PrfItem { /// ## Merge type (enhance) /// create the enhanced item by using `merge` rule - pub fn from_merge(name: String, desc: String) -> Result { + pub fn from_merge() -> Result { let uid = help::get_uid("m"); let file = format!("{uid}.yaml"); Ok(PrfItem { uid: Some(uid), itype: Some("merge".into()), - name: Some(name), - desc: Some(desc), + name: None, + desc: None, file: Some(file), url: None, selected: None, @@ -371,15 +448,15 @@ impl PrfItem { /// ## Script type (enhance) /// create the enhanced item by using javascript quick.js - pub fn from_script(name: String, desc: String) -> Result { + pub fn from_script() -> Result { let uid = help::get_uid("s"); let file = format!("{uid}.js"); // js ext Ok(PrfItem { uid: Some(uid), itype: Some("script".into()), - name: Some(name), - desc: Some(desc), + name: None, + desc: None, file: Some(file), url: None, home: None, @@ -391,6 +468,69 @@ impl PrfItem { }) } + /// ## Rules type (enhance) + pub fn from_rules() -> Result { + let uid = help::get_uid("r"); + let file = format!("{uid}.yaml"); // yaml ext + + Ok(PrfItem { + uid: Some(uid), + itype: Some("rules".into()), + name: None, + desc: None, + file: Some(file), + url: None, + home: None, + selected: None, + extra: None, + option: None, + updated: Some(chrono::Local::now().timestamp() as usize), + file_data: Some(tmpl::ITEM_RULES.into()), + }) + } + + /// ## Proxies type (enhance) + pub fn from_proxies() -> Result { + let uid = help::get_uid("p"); + let file = format!("{uid}.yaml"); // yaml ext + + Ok(PrfItem { + uid: Some(uid), + itype: Some("proxies".into()), + name: None, + desc: None, + file: Some(file), + url: None, + home: None, + selected: None, + extra: None, + option: None, + updated: Some(chrono::Local::now().timestamp() as usize), + file_data: Some(tmpl::ITEM_PROXIES.into()), + }) + } + + /// ## Groups type (enhance) + pub fn from_groups() -> Result { + let uid = help::get_uid("g"); + let file = format!("{uid}.yaml"); // yaml ext + + Ok(PrfItem { + uid: Some(uid), + itype: Some("groups".into()), + name: None, + desc: None, + file: Some(file), + url: None, + home: None, + selected: None, + extra: None, + option: None, + updated: Some(chrono::Local::now().timestamp() as usize), + file_data: Some(tmpl::ITEM_GROUPS.into()), + }) + } + /// get the file data pub fn read_file(&self) -> Result { if self.file.is_none() { diff --git a/clash-verge-rev/src-tauri/src/config/profiles.rs b/clash-verge-rev/src-tauri/src/config/profiles.rs index 309665168c..08ef1f9eb9 100644 --- a/clash-verge-rev/src-tauri/src/config/profiles.rs +++ b/clash-verge-rev/src-tauri/src/config/profiles.rs @@ -11,9 +11,6 @@ pub struct IProfiles { /// same as PrfConfig.current pub current: Option, - /// same as PrfConfig.chain - pub chain: Option>, - /// profile list pub items: Option>, } @@ -80,10 +77,6 @@ impl IProfiles { } } - if let Some(chain) = patch.chain { - self.chain = Some(chain); - } - Ok(()) } @@ -243,9 +236,19 @@ impl IProfiles { pub fn delete_item(&mut self, uid: String) -> Result { let current = self.current.as_ref().unwrap_or(&uid); let current = current.clone(); - + let item = self.get_item(&uid)?; + let merge_uid = item.option.as_ref().and_then(|e| e.merge.clone()); + let script_uid = item.option.as_ref().and_then(|e| e.script.clone()); + let rules_uid = item.option.as_ref().and_then(|e| e.rules.clone()); + let proxies_uid = item.option.as_ref().and_then(|e| e.proxies.clone()); + let groups_uid = item.option.as_ref().and_then(|e| e.groups.clone()); let mut items = self.items.take().unwrap_or_default(); let mut index = None; + let mut merge_index = None; + let mut script_index = None; + let mut rules_index = None; + let mut proxies_index = None; + let mut groups_index = None; // get the index for (i, _) in items.iter().enumerate() { @@ -254,7 +257,6 @@ impl IProfiles { break; } } - if let Some(index) = index { if let Some(file) = items.remove(index).file { let _ = dirs::app_profiles_dir().map(|path| { @@ -265,7 +267,91 @@ impl IProfiles { }); } } - + // get the merge index + for (i, _) in items.iter().enumerate() { + if items[i].uid == merge_uid { + merge_index = Some(i); + break; + } + } + if let Some(index) = merge_index { + if let Some(file) = items.remove(index).file { + let _ = dirs::app_profiles_dir().map(|path| { + let path = path.join(file); + if path.exists() { + let _ = fs::remove_file(path); + } + }); + } + } + // get the script index + for (i, _) in items.iter().enumerate() { + if items[i].uid == script_uid { + script_index = Some(i); + break; + } + } + if let Some(index) = script_index { + if let Some(file) = items.remove(index).file { + let _ = dirs::app_profiles_dir().map(|path| { + let path = path.join(file); + if path.exists() { + let _ = fs::remove_file(path); + } + }); + } + } + // get the rules index + for (i, _) in items.iter().enumerate() { + if items[i].uid == rules_uid { + rules_index = Some(i); + break; + } + } + if let Some(index) = rules_index { + if let Some(file) = items.remove(index).file { + let _ = dirs::app_profiles_dir().map(|path| { + let path = path.join(file); + if path.exists() { + let _ = fs::remove_file(path); + } + }); + } + } + // get the proxies index + for (i, _) in items.iter().enumerate() { + if items[i].uid == proxies_uid { + proxies_index = Some(i); + break; + } + } + if let Some(index) = proxies_index { + if let Some(file) = items.remove(index).file { + let _ = dirs::app_profiles_dir().map(|path| { + let path = path.join(file); + if path.exists() { + let _ = fs::remove_file(path); + } + }); + } + } + // get the groups index + for (i, _) in items.iter().enumerate() { + if items[i].uid == groups_uid { + groups_index = Some(i); + break; + } + } + if let Some(index) = groups_index { + if let Some(file) = items.remove(index).file { + let _ = dirs::app_profiles_dir().map(|path| { + let path = path.join(file); + if path.exists() { + let _ = fs::remove_file(path); + } + }); + } + } // delete the original uid if current == uid { self.current = match !items.is_empty() { @@ -288,11 +374,81 @@ impl IProfiles { Some(file) => dirs::app_profiles_dir()?.join(file), None => bail!("failed to get the file field"), }; - return help::read_merge_mapping(&file_path); + return help::read_mapping(&file_path); } bail!("failed to find the current profile \"uid:{current}\""); } _ => Ok(Mapping::new()), } } + + /// 获取current指向的订阅的merge + pub fn current_merge(&self) -> Option { + match (self.current.as_ref(), self.items.as_ref()) { + (Some(current), Some(items)) => { + if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) { + let merge = item.option.as_ref().and_then(|e| e.merge.clone()); + return merge; + } + None + } + _ => None, + } + } + + /// 获取current指向的订阅的script + pub fn current_script(&self) -> Option { + match (self.current.as_ref(), self.items.as_ref()) { + (Some(current), Some(items)) => { + if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) { + let script = item.option.as_ref().and_then(|e| e.script.clone()); + return script; + } + None + } + _ => None, + } + } + + /// 获取current指向的订阅的rules + pub fn current_rules(&self) -> Option { + match (self.current.as_ref(), self.items.as_ref()) { + (Some(current), Some(items)) => { + if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) { + let rules = item.option.as_ref().and_then(|e| e.rules.clone()); + return rules; + } + None + } + _ => None, + } + } + + /// 获取current指向的订阅的proxies + pub fn current_proxies(&self) -> Option { + match (self.current.as_ref(), self.items.as_ref()) { + (Some(current), Some(items)) => { + if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) { + let proxies = item.option.as_ref().and_then(|e| e.proxies.clone()); + return proxies; + } + None + } + _ => None, + } + } + + /// 获取current指向的订阅的groups + pub fn current_groups(&self) -> Option { + match (self.current.as_ref(), self.items.as_ref()) { + (Some(current), Some(items)) => { + if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) { + let groups = item.option.as_ref().and_then(|e| e.groups.clone()); + return groups; + } + None + } + _ => None, + } + } } diff --git a/clash-verge-rev/src-tauri/src/core/core.rs b/clash-verge-rev/src-tauri/src/core/core.rs index 51e665def4..1fa76127ea 100644 --- a/clash-verge-rev/src-tauri/src/core/core.rs +++ b/clash-verge-rev/src-tauri/src/core/core.rs @@ -219,22 +219,18 @@ impl CoreManager { } /// 停止核心运行 - pub fn stop_core(&self) -> Result<()> { + pub async fn stop_core(&self) -> Result<()> { // 关闭tun模式 - tauri::async_runtime::block_on(async move { - let mut disable = Mapping::new(); - let mut tun = Mapping::new(); - tun.insert("enable".into(), false.into()); - disable.insert("tun".into(), tun.into()); - log::debug!(target: "app", "disable tun mode"); - let _ = clash_api::patch_configs(&disable).await; - }); + let mut disable = Mapping::new(); + let mut tun = Mapping::new(); + tun.insert("enable".into(), false.into()); + disable.insert("tun".into(), tun.into()); + log::debug!(target: "app", "disable tun mode"); + let _ = clash_api::patch_configs(&disable).await; if *self.use_service_mode.lock() { log::debug!(target: "app", "stop the core by service"); - tauri::async_runtime::block_on(async move { - log_err!(service::stop_core_by_service().await); - }); + log_err!(service::stop_core_by_service().await); return Ok(()); } @@ -265,7 +261,7 @@ impl CoreManager { Config::verge().draft().clash_core = Some(clash_core); // 更新订阅 - Config::generate()?; + Config::generate().await?; self.check_config()?; @@ -293,7 +289,7 @@ impl CoreManager { log::debug!(target: "app", "try to update clash config"); // 更新订阅 - Config::generate()?; + Config::generate().await?; // 检查订阅是否正常 self.check_config()?; diff --git a/clash-verge-rev/src-tauri/src/core/service.rs b/clash-verge-rev/src-tauri/src/core/service.rs index cf00895c85..4f9db5fee1 100644 --- a/clash-verge-rev/src-tauri/src/core/service.rs +++ b/clash-verge-rev/src-tauri/src/core/service.rs @@ -314,14 +314,16 @@ pub(super) async fn stop_core_by_service() -> Result<()> { } /// set dns by service -pub fn set_dns_by_service() -> Result<()> { +pub async fn set_dns_by_service() -> Result<()> { let url = format!("{SERVICE_URL}/set_dns"); - let res = reqwest::blocking::ClientBuilder::new() + let res = reqwest::ClientBuilder::new() .no_proxy() .build()? .post(url) - .send()? + .send() + .await? .json::() + .await .context("failed to connect to the Clash Verge Service")?; if res.code != 0 { @@ -332,14 +334,16 @@ pub fn set_dns_by_service() -> Result<()> { } /// unset dns by service -pub fn unset_dns_by_service() -> Result<()> { +pub async fn unset_dns_by_service() -> Result<()> { let url = format!("{SERVICE_URL}/unset_dns"); - let res = reqwest::blocking::ClientBuilder::new() + let res = reqwest::ClientBuilder::new() .no_proxy() .build()? .post(url) - .send()? + .send() + .await? .json::() + .await .context("failed to connect to the Clash Verge Service")?; if res.code != 0 { diff --git a/clash-verge-rev/src-tauri/src/enhance/chain.rs b/clash-verge-rev/src-tauri/src/enhance/chain.rs index bc88d420f6..2fb7e95129 100644 --- a/clash-verge-rev/src-tauri/src/enhance/chain.rs +++ b/clash-verge-rev/src-tauri/src/enhance/chain.rs @@ -1,3 +1,4 @@ +use super::SeqMap; use crate::{ config::PrfItem, utils::{dirs, help}, @@ -15,6 +16,9 @@ pub struct ChainItem { pub enum ChainType { Merge(Mapping), Script(String), + Rules(SeqMap), + Proxies(SeqMap), + Groups(SeqMap), } #[derive(Debug, Clone)] @@ -43,7 +47,19 @@ impl From<&PrfItem> for Option { }), "merge" => Some(ChainItem { uid, - data: ChainType::Merge(help::read_merge_mapping(&path).ok()?), + data: ChainType::Merge(help::read_mapping(&path).ok()?), + }), + "rules" => Some(ChainItem { + uid, + data: ChainType::Rules(help::read_seq_map(&path).ok()?), + }), + "proxies" => Some(ChainItem { + uid, + data: ChainType::Proxies(help::read_seq_map(&path).ok()?), + }), + "groups" => Some(ChainItem { + uid, + data: ChainType::Groups(help::read_seq_map(&path).ok()?), }), _ => None, } diff --git a/clash-verge-rev/src-tauri/src/enhance/mod.rs b/clash-verge-rev/src-tauri/src/enhance/mod.rs index f93e841a6f..ee4f99c5ed 100644 --- a/clash-verge-rev/src-tauri/src/enhance/mod.rs +++ b/clash-verge-rev/src-tauri/src/enhance/mod.rs @@ -2,14 +2,17 @@ mod chain; pub mod field; mod merge; mod script; +pub mod seq; mod tun; use self::chain::*; use self::field::*; use self::merge::*; use self::script::*; +use self::seq::*; use self::tun::*; use crate::config::Config; +use crate::utils::tmpl; use serde_yaml::Mapping; use std::collections::HashMap; use std::collections::HashSet; @@ -18,7 +21,7 @@ type ResultLog = Vec<(String, String)>; /// Enhance mode /// 返回最终订阅、该订阅包含的键、和script执行的结果 -pub fn enhance() -> (Mapping, Vec, HashMap) { +pub async fn enhance() -> (Mapping, Vec, HashMap) { // config.yaml 的订阅 let clash_config = { Config::clash().latest().0.clone() }; @@ -47,33 +50,85 @@ pub fn enhance() -> (Mapping, Vec, HashMap) { }; // 从profiles里拿东西 - let (mut config, chain) = { + let (mut config, merge_item, script_item, rules_item, proxies_item, groups_item) = { let profiles = Config::profiles(); let profiles = profiles.latest(); let current = profiles.current_mapping().unwrap_or_default(); + let merge = profiles + .get_item(&profiles.current_merge().unwrap_or_default()) + .ok() + .and_then(>::from) + .unwrap_or_else(|| ChainItem { + uid: "".into(), + data: ChainType::Merge(Mapping::new()), + }); + let script = profiles + .get_item(&profiles.current_script().unwrap_or_default()) + .ok() + .and_then(>::from) + .unwrap_or_else(|| ChainItem { + uid: "".into(), + data: ChainType::Script(tmpl::ITEM_SCRIPT.into()), + }); + let rules = profiles + .get_item(&profiles.current_rules().unwrap_or_default()) + .ok() + .and_then(>::from) + .unwrap_or_else(|| ChainItem { + uid: "".into(), + data: ChainType::Rules(SeqMap::default()), + }); + let proxies = profiles + .get_item(&profiles.current_proxies().unwrap_or_default()) + .ok() + .and_then(>::from) + .unwrap_or_else(|| ChainItem { + uid: "".into(), + data: ChainType::Proxies(SeqMap::default()), + }); + let groups = profiles + .get_item(&profiles.current_groups().unwrap_or_default()) + .ok() + .and_then(>::from) + .unwrap_or_else(|| ChainItem { + uid: "".into(), + data: ChainType::Groups(SeqMap::default()), + }); - let chain = match profiles.chain.as_ref() { - Some(chain) => chain - .iter() - .filter_map(|uid| profiles.get_item(uid).ok()) - .filter_map(>::from) - .collect::>(), - None => vec![], - }; - - (current, chain) + (current, merge, script, rules, proxies, groups) }; let mut result_map = HashMap::new(); // 保存脚本日志 let mut exists_keys = use_keys(&config); // 保存出现过的keys // 处理用户的profile - chain.into_iter().for_each(|item| match item.data { + match rules_item.data { + ChainType::Rules(rules) => { + config = use_seq(rules, config.to_owned(), "rules"); + } + _ => {} + } + match proxies_item.data { + ChainType::Proxies(proxies) => { + config = use_seq(proxies, config.to_owned(), "proxies"); + } + _ => {} + } + match groups_item.data { + ChainType::Groups(groups) => { + config = use_seq(groups, config.to_owned(), "proxy-groups"); + } + _ => {} + } + match merge_item.data { ChainType::Merge(merge) => { exists_keys.extend(use_keys(&merge)); config = use_merge(merge, config.to_owned()); } + _ => {} + } + match script_item.data { ChainType::Script(script) => { let mut logs = vec![]; @@ -86,9 +141,10 @@ pub fn enhance() -> (Mapping, Vec, HashMap) { Err(err) => logs.push(("exception".into(), err.to_string())), } - result_map.insert(item.uid, logs); + result_map.insert(script_item.uid, logs); } - }); + _ => {} + } // 合并默认的config for (key, value) in clash_config.into_iter() { @@ -149,7 +205,7 @@ pub fn enhance() -> (Mapping, Vec, HashMap) { }); } - config = use_tun(config, enable_tun); + config = use_tun(config, enable_tun).await; config = use_sort(config); let mut exists_set = HashSet::new(); diff --git a/clash-verge-rev/src-tauri/src/enhance/seq.rs b/clash-verge-rev/src-tauri/src/enhance/seq.rs new file mode 100644 index 0000000000..51c74f1623 --- /dev/null +++ b/clash-verge-rev/src-tauri/src/enhance/seq.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Serialize}; +use serde_yaml::{Mapping, Sequence, Value}; +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct SeqMap { + prepend: Sequence, + append: Sequence, + delete: Sequence, +} + +pub fn use_seq(seq_map: SeqMap, config: Mapping, name: &str) -> Mapping { + let prepend = seq_map.prepend; + let append = seq_map.append; + let delete = seq_map.delete; + + let origin_seq = config.get(&name).map_or(Sequence::default(), |val| { + val.as_sequence().unwrap().clone() + }); + let mut seq = origin_seq.clone(); + + for item in prepend { + seq.insert(0, item); + } + + for item in append { + seq.push(item); + } + + for item in delete { + seq.retain(|x| x != &item); + } + let mut config = config.clone(); + config.insert(Value::from(name), Value::from(seq)); + return config; +} diff --git a/clash-verge-rev/src-tauri/src/enhance/tun.rs b/clash-verge-rev/src-tauri/src/enhance/tun.rs index 17e74a2a49..2e9de2595b 100644 --- a/clash-verge-rev/src-tauri/src/enhance/tun.rs +++ b/clash-verge-rev/src-tauri/src/enhance/tun.rs @@ -1,4 +1,4 @@ -use crate::core::service; +use crate::{core::service, log_err}; use serde_yaml::{Mapping, Value}; macro_rules! revise { @@ -18,7 +18,7 @@ macro_rules! append { }; } -pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping { +pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping { let tun_key = Value::from("tun"); let tun_val = config.get(&tun_key); @@ -35,10 +35,10 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping { revise!(config, "tun", tun_val); if enable { - let _ = service::set_dns_by_service(); + log_err!(service::set_dns_by_service().await); use_dns_for_tun(config) } else { - let _ = service::unset_dns_by_service(); + log_err!(service::unset_dns_by_service().await); config } } diff --git a/clash-verge-rev/src-tauri/src/feat.rs b/clash-verge-rev/src-tauri/src/feat.rs index 8ac9f09526..57a192871c 100644 --- a/clash-verge-rev/src-tauri/src/feat.rs +++ b/clash-verge-rev/src-tauri/src/feat.rs @@ -139,7 +139,7 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> { || patch.get("secret").is_some() || patch.get("external-controller").is_some() { - Config::generate()?; + Config::generate().await?; CoreManager::global().run_core().await?; handle::Handle::refresh_clash(); } @@ -200,23 +200,23 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> { if service_mode.is_some() { log::debug!(target: "app", "change service mode to {}", service_mode.unwrap()); - Config::generate()?; + Config::generate().await?; CoreManager::global().run_core().await?; } else if tun_mode.is_some() { update_core_config().await?; } #[cfg(not(target_os = "windows"))] if redir_enabled.is_some() { - Config::generate()?; + Config::generate().await?; CoreManager::global().run_core().await?; } #[cfg(target_os = "linux")] if tproxy_enabled.is_some() { - Config::generate()?; + Config::generate().await?; CoreManager::global().run_core().await?; } if socks_enabled.is_some() || http_enabled.is_some() { - Config::generate()?; + Config::generate().await?; CoreManager::global().run_core().await?; } if auto_launch.is_some() { @@ -292,7 +292,6 @@ pub async fn update_profile(uid: String, option: Option) -> Result<() Some((url, opt)) => { let merged_opt = PrfOption::merge(opt, option); let item = PrfItem::from_url(&url, None, None, merged_opt).await?; - let profiles = Config::profiles(); let mut profiles = profiles.latest(); profiles.update_item(uid.clone(), item)?; diff --git a/clash-verge-rev/src-tauri/src/main.rs b/clash-verge-rev/src-tauri/src/main.rs index 032f1764bb..dbdd162b26 100644 --- a/clash-verge-rev/src-tauri/src/main.rs +++ b/clash-verge-rev/src-tauri/src/main.rs @@ -15,8 +15,15 @@ use tauri::{api, SystemTray}; fn main() -> std::io::Result<()> { // 单例检测 - if server::check_singleton().is_err() { - println!("app exists"); + let app_exists: bool = tauri::async_runtime::block_on(async move { + if server::check_singleton().await.is_err() { + println!("app exists"); + true + } else { + false + } + }); + if app_exists { return Ok(()); } @@ -29,7 +36,9 @@ fn main() -> std::io::Result<()> { let mut builder = tauri::Builder::default() .system_tray(SystemTray::new()) .setup(|app| { - resolve::resolve_setup(app); + tauri::async_runtime::block_on(async move { + resolve::resolve_setup(app).await; + }); Ok(()) }) .on_system_tray_event(core::tray::Tray::on_system_tray_event) diff --git a/clash-verge-rev/src-tauri/src/utils/help.rs b/clash-verge-rev/src-tauri/src/utils/help.rs index a49eb1da2a..7c175de89d 100644 --- a/clash-verge-rev/src-tauri/src/utils/help.rs +++ b/clash-verge-rev/src-tauri/src/utils/help.rs @@ -1,3 +1,4 @@ +use crate::enhance::seq::SeqMap; use anyhow::{anyhow, bail, Context, Result}; use nanoid::nanoid; use serde::{de::DeserializeOwned, Serialize}; @@ -26,7 +27,7 @@ pub fn read_yaml(path: &PathBuf) -> Result { } /// read mapping from yaml fix #165 -pub fn read_merge_mapping(path: &PathBuf) -> Result { +pub fn read_mapping(path: &PathBuf) -> Result { let mut val: Value = read_yaml(path)?; val.apply_merge() .with_context(|| format!("failed to apply merge \"{}\"", path.display()))?; @@ -40,6 +41,13 @@ pub fn read_merge_mapping(path: &PathBuf) -> Result { .to_owned()) } +/// read mapping from yaml fix #165 +pub fn read_seq_map(path: &PathBuf) -> Result { + let val: SeqMap = read_yaml(path)?; + + Ok(val) +} + /// save the data to the file /// can set `prefix` string to add some comments pub fn save_yaml(path: &PathBuf, data: &T, prefix: Option<&str>) -> Result<()> { diff --git a/clash-verge-rev/src-tauri/src/utils/resolve.rs b/clash-verge-rev/src-tauri/src/utils/resolve.rs index 82a39a7f6f..8d22c2970f 100644 --- a/clash-verge-rev/src-tauri/src/utils/resolve.rs +++ b/clash-verge-rev/src-tauri/src/utils/resolve.rs @@ -1,10 +1,6 @@ -use crate::config::{IVerge, PrfOption}; -use crate::{ - config::{Config, PrfItem}, - core::*, - utils::init, - utils::server, -}; +use crate::cmds::import_profile; +use crate::config::IVerge; +use crate::{config::Config, core::*, utils::init, utils::server}; use crate::{log_err, trace_err}; use anyhow::Result; use once_cell::sync::OnceCell; @@ -34,7 +30,7 @@ pub fn find_unused_port() -> Result { } /// handle something when start app -pub fn resolve_setup(app: &mut App) { +pub async fn resolve_setup(app: &mut App) { #[cfg(target_os = "macos")] app.set_activation_policy(tauri::ActivationPolicy::Accessory); let version = app.package_info().version.to_string(); @@ -73,7 +69,8 @@ pub fn resolve_setup(app: &mut App) { // 启动核心 log::trace!("init config"); - log_err!(Config::init_config()); + + log_err!(Config::init_config().await); log::trace!("launch core"); log_err!(CoreManager::global().init()); @@ -101,9 +98,7 @@ pub fn resolve_setup(app: &mut App) { if argvs.len() > 1 { let param = argvs[1].as_str(); if param.starts_with("clash:") { - tauri::async_runtime::block_on(async { - resolve_scheme(argvs[1].to_owned()).await; - }); + log_err!(resolve_scheme(argvs[1].to_owned()).await); } } } @@ -111,8 +106,10 @@ pub fn resolve_setup(app: &mut App) { /// reset system proxy pub fn resolve_reset() { log_err!(sysopt::Sysopt::global().reset_sysproxy()); - log_err!(CoreManager::global().stop_core()); - let _ = service::unset_dns_by_service(); + tauri::async_runtime::block_on(async move { + log_err!(CoreManager::global().stop_core().await); + log_err!(service::unset_dns_by_service().await); + }); } /// create main window @@ -239,31 +236,26 @@ pub fn save_window_size_position(app_handle: &AppHandle, save_to_file: bool) -> Ok(()) } -pub async fn resolve_scheme(param: String) { +pub async fn resolve_scheme(param: String) -> Result<()> { let url = param .trim_start_matches("clash://install-config/?url=") .trim_start_matches("clash://install-config?url="); - let option = PrfOption { - user_agent: None, - with_proxy: Some(true), - self_proxy: None, - danger_accept_invalid_certs: None, - update_interval: None, - }; - if let Ok(item) = PrfItem::from_url(url, None, None, Some(option)).await { - if Config::profiles().data().append_item(item).is_ok() { + match import_profile(url.to_string(), None).await { + Ok(_) => { notification::Notification::new(crate::utils::dirs::APP_ID) .title("Clash Verge") .body("Import profile success") .show() .unwrap(); - }; - } else { - notification::Notification::new(crate::utils::dirs::APP_ID) - .title("Clash Verge") - .body("Import profile failed") - .show() - .unwrap(); - log::error!("failed to parse url: {}", url); + } + Err(e) => { + notification::Notification::new(crate::utils::dirs::APP_ID) + .title("Clash Verge") + .body(format!("Import profile failed: {e}")) + .show() + .unwrap(); + log::error!("Import profile failed: {e}"); + } } + Ok(()) } diff --git a/clash-verge-rev/src-tauri/src/utils/server.rs b/clash-verge-rev/src-tauri/src/utils/server.rs index daf9fb8650..ac16b67f36 100644 --- a/clash-verge-rev/src-tauri/src/utils/server.rs +++ b/clash-verge-rev/src-tauri/src/utils/server.rs @@ -1,7 +1,10 @@ extern crate warp; use super::resolve; -use crate::config::{Config, IVerge, DEFAULT_PAC}; +use crate::{ + config::{Config, IVerge, DEFAULT_PAC}, + log_err, +}; use anyhow::{bail, Result}; use port_scanner::local_port_available; use std::convert::Infallible; @@ -14,40 +17,38 @@ struct QueryParam { } /// check whether there is already exists -pub fn check_singleton() -> Result<()> { +pub async fn check_singleton() -> Result<()> { let port = IVerge::get_singleton_port(); if !local_port_available(port) { - tauri::async_runtime::block_on(async { - let resp = reqwest::get(format!("http://127.0.0.1:{port}/commands/ping")) - .await? - .text() - .await?; + let resp = reqwest::get(format!("http://127.0.0.1:{port}/commands/ping")) + .await? + .text() + .await?; - if &resp == "ok" { - let argvs: Vec = std::env::args().collect(); - if argvs.len() > 1 { - let param = argvs[1].as_str(); - if param.starts_with("clash:") { - reqwest::get(format!( - "http://127.0.0.1:{port}/commands/scheme?param={param}" - )) - .await? - .text() - .await?; - } - } else { - reqwest::get(format!("http://127.0.0.1:{port}/commands/visible")) - .await? - .text() - .await?; + if &resp == "ok" { + let argvs: Vec = std::env::args().collect(); + if argvs.len() > 1 { + let param = argvs[1].as_str(); + if param.starts_with("clash:") { + reqwest::get(format!( + "http://127.0.0.1:{port}/commands/scheme?param={param}" + )) + .await? + .text() + .await?; } - bail!("app exists"); + } else { + reqwest::get(format!("http://127.0.0.1:{port}/commands/visible")) + .await? + .text() + .await?; } + bail!("app exists"); + } - log::error!("failed to setup singleton listen server"); - Ok(()) - }) + log::error!("failed to setup singleton listen server"); + Ok(()) } else { Ok(()) } @@ -87,7 +88,7 @@ pub fn embed_server(app_handle: AppHandle) { .and_then(scheme_handler); async fn scheme_handler(query: QueryParam) -> Result { - resolve::resolve_scheme(query.param).await; + log_err!(resolve::resolve_scheme(query.param).await); Ok("ok") } let commands = ping.or(visible).or(pac).or(scheme); diff --git a/clash-verge-rev/src-tauri/src/utils/tmpl.rs b/clash-verge-rev/src-tauri/src/utils/tmpl.rs index b4b801ac71..45a018bac7 100644 --- a/clash-verge-rev/src-tauri/src/utils/tmpl.rs +++ b/clash-verge-rev/src-tauri/src/utils/tmpl.rs @@ -33,3 +33,33 @@ function main(config) { return config; } "; + +/// enhanced profile +pub const ITEM_RULES: &str = "# Profile Enhancement Rules Template for Clash Verge + +prepend: [] + +append: [] + +delete: [] +"; + +/// enhanced profile +pub const ITEM_PROXIES: &str = "# Profile Enhancement Proxies Template for Clash Verge + +prepend: [] + +append: [] + +delete: [] +"; + +/// enhanced profile +pub const ITEM_GROUPS: &str = "# Profile Enhancement Groups Template for Clash Verge + +prepend: [] + +append: [] + +delete: [] +"; diff --git a/clash-verge-rev/src/components/profile/confirm-viewer.tsx b/clash-verge-rev/src/components/profile/confirm-viewer.tsx index 382998b983..7610abea10 100644 --- a/clash-verge-rev/src/components/profile/confirm-viewer.tsx +++ b/clash-verge-rev/src/components/profile/confirm-viewer.tsx @@ -27,10 +27,10 @@ export const ConfirmViewer = (props: Props) => { return ( - {t(title)} + {title} - {t(message)} + {message} diff --git a/clash-verge-rev/src/components/profile/editor-viewer.tsx b/clash-verge-rev/src/components/profile/editor-viewer.tsx index c64010f8f8..c1351d667b 100644 --- a/clash-verge-rev/src/components/profile/editor-viewer.tsx +++ b/clash-verge-rev/src/components/profile/editor-viewer.tsx @@ -32,7 +32,7 @@ interface Props { language: "yaml" | "javascript" | "css"; schema?: "clash" | "merge"; onClose: () => void; - onChange?: (content?: string) => void; + onChange?: (prev?: string, curr?: string) => void; } // yaml worker @@ -90,6 +90,7 @@ export const EditorViewer = (props: Props) => { const editorRef = useRef(); const instanceRef = useRef(null); const themeMode = useThemeMode(); + const prevData = useRef(); useEffect(() => { if (!open) return; @@ -136,6 +137,8 @@ export const EditorViewer = (props: Props) => { fontLigatures: true, // 连字符 smoothScrolling: true, // 平滑滚动 }); + + prevData.current = data; }); return () => { @@ -147,15 +150,15 @@ export const EditorViewer = (props: Props) => { }, [open]); const onSave = useLockFn(async () => { - const value = instanceRef.current?.getValue(); + const currData = instanceRef.current?.getValue(); - if (value == null) return; + if (currData == null) return; try { if (mode === "profile") { - await saveProfileFile(property, value); + await saveProfileFile(property, currData); } - onChange?.(value); + onChange?.(prevData.current, currData); onClose(); } catch (err: any) { Notice.error(err.message || err.toString()); diff --git a/clash-verge-rev/src/components/profile/profile-item.tsx b/clash-verge-rev/src/components/profile/profile-item.tsx index 253c240525..7d659f851e 100644 --- a/clash-verge-rev/src/components/profile/profile-item.tsx +++ b/clash-verge-rev/src/components/profile/profile-item.tsx @@ -17,7 +17,7 @@ import { } from "@mui/material"; import { RefreshRounded, DragIndicator } from "@mui/icons-material"; import { useLoadingCache, useSetLoadingCache } from "@/services/states"; -import { updateProfile, deleteProfile, viewProfile } from "@/services/cmds"; +import { updateProfile, viewProfile } from "@/services/cmds"; import { Notice } from "@/components/base"; import { EditorViewer } from "@/components/profile/editor-viewer"; import { ProfileBox } from "./profile-box"; @@ -36,10 +36,20 @@ interface Props { itemData: IProfileItem; onSelect: (force: boolean) => void; onEdit: () => void; + onChange?: (prev?: string, curr?: string) => void; + onDelete: () => void; } export const ProfileItem = (props: Props) => { - const { selected, activating, itemData, onSelect, onEdit } = props; + const { + selected, + activating, + itemData, + onSelect, + onEdit, + onChange, + onDelete, + } = props; const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: props.id }); @@ -49,10 +59,11 @@ export const ProfileItem = (props: Props) => { const loadingCache = useLoadingCache(); const setLoadingCache = useSetLoadingCache(); - const { uid, name = "Profile", extra, updated = 0 } = itemData; + const { uid, name = "Profile", extra, updated = 0, option } = itemData; // local file mode // remote file mode + // remote file mode const hasUrl = !!itemData.url; const hasExtra = !!extra; // only subscription url has extra info const hasHome = !!itemData.home; // only subscription url has home page @@ -94,6 +105,11 @@ export const ProfileItem = (props: Props) => { }, [hasUrl, updated]); const [fileOpen, setFileOpen] = useState(false); + const [rulesOpen, setRulesOpen] = useState(false); + const [proxiesOpen, setProxiesOpen] = useState(false); + const [groupsOpen, setGroupsOpen] = useState(false); + const [mergeOpen, setMergeOpen] = useState(false); + const [scriptOpen, setScriptOpen] = useState(false); const [confirmOpen, setConfirmOpen] = useState(false); const onOpenHome = () => { @@ -111,6 +127,31 @@ export const ProfileItem = (props: Props) => { setFileOpen(true); }; + const onEditRules = () => { + setAnchorEl(null); + setRulesOpen(true); + }; + + const onEditProxies = () => { + setAnchorEl(null); + setProxiesOpen(true); + }; + + const onEditGroups = () => { + setAnchorEl(null); + setGroupsOpen(true); + }; + + const onEditMerge = () => { + setAnchorEl(null); + setMergeOpen(true); + }; + + const onEditScript = () => { + setAnchorEl(null); + setScriptOpen(true); + }; + const onForceSelect = () => { setAnchorEl(null); onSelect(true); @@ -162,44 +203,86 @@ export const ProfileItem = (props: Props) => { } }); - const onDelete = useLockFn(async () => { - setAnchorEl(null); - try { - await deleteProfile(itemData.uid); - mutate("getProfiles"); - } catch (err: any) { - Notice.error(err?.message || err.toString()); - } - }); - const urlModeMenu = ( - hasHome ? [{ label: "Home", handler: onOpenHome }] : [] + hasHome ? [{ label: "Home", handler: onOpenHome, disabled: false }] : [] ).concat([ - { label: "Select", handler: onForceSelect }, - { label: "Edit Info", handler: onEditInfo }, - { label: "Edit File", handler: onEditFile }, - { label: "Open File", handler: onOpenFile }, - { label: "Update", handler: () => onUpdate(0) }, - { label: "Update(Proxy)", handler: () => onUpdate(2) }, + { label: "Select", handler: onForceSelect, disabled: false }, + { label: "Edit Info", handler: onEditInfo, disabled: false }, + { label: "Edit File", handler: onEditFile, disabled: false }, + { + label: "Edit Rules", + handler: onEditRules, + disabled: option?.rules === null, + }, + { + label: "Edit Proxies", + handler: onEditProxies, + disabled: option?.proxies === null, + }, + { + label: "Edit Groups", + handler: onEditGroups, + disabled: option?.groups === null, + }, + { + label: "Edit Merge", + handler: onEditMerge, + disabled: option?.merge === null, + }, + { + label: "Edit Script", + handler: onEditScript, + disabled: option?.script === null, + }, + { label: "Open File", handler: onOpenFile, disabled: false }, + { label: "Update", handler: () => onUpdate(0), disabled: false }, + { label: "Update(Proxy)", handler: () => onUpdate(2), disabled: false }, { label: "Delete", handler: () => { setAnchorEl(null); setConfirmOpen(true); }, + disabled: false, }, ]); const fileModeMenu = [ - { label: "Select", handler: onForceSelect }, - { label: "Edit Info", handler: onEditInfo }, - { label: "Edit File", handler: onEditFile }, - { label: "Open File", handler: onOpenFile }, + { label: "Select", handler: onForceSelect, disabled: false }, + { label: "Edit Info", handler: onEditInfo, disabled: false }, + { label: "Edit File", handler: onEditFile, disabled: false }, + { + label: "Edit Rules", + handler: onEditRules, + disabled: option?.rules === null, + }, + { + label: "Edit Proxies", + handler: onEditProxies, + disabled: option?.proxies === null, + }, + { + label: "Edit Groups", + handler: onEditGroups, + disabled: option?.groups === null, + }, + { + label: "Edit Merge", + handler: onEditMerge, + disabled: option?.merge === null, + }, + { + label: "Edit Script", + handler: onEditScript, + disabled: option?.script === null, + }, + { label: "Open File", handler: onOpenFile, disabled: false }, { label: "Delete", handler: () => { setAnchorEl(null); setConfirmOpen(true); }, + disabled: false, }, ]; @@ -242,7 +325,7 @@ export const ProfileItem = (props: Props) => { backdropFilter: "blur(2px)", }} > - + )} @@ -312,7 +395,7 @@ export const ProfileItem = (props: Props) => { ) : ( hasUrl && ( - + {from} ) @@ -323,7 +406,7 @@ export const ProfileItem = (props: Props) => { flex="1 0 auto" fontSize={14} textAlign="right" - title={`Updated Time: ${parseExpire(updated)}`} + title={`${t("Update Time")}: ${parseExpire(updated)}`} > {updated > 0 ? dayjs(updated * 1000).fromNow() : ""} @@ -334,17 +417,21 @@ export const ProfileItem = (props: Props) => { {/* the third line show extra info or last updated time */} {hasExtra ? ( - + {parseTraffic(upload + download)} / {parseTraffic(total)} - {expire} + {expire} ) : ( - {parseExpire(updated)} + {parseExpire(updated)} )} - + 0 ? 1 : 0 }} + /> { { open={fileOpen} language="yaml" schema="clash" + onChange={onChange} onClose={() => setFileOpen(false)} /> + setRulesOpen(false)} + /> + setProxiesOpen(false)} + /> + setGroupsOpen(false)} + /> + setMergeOpen(false)} + /> + setScriptOpen(false)} + /> setConfirmOpen(false)} onConfirm={() => { diff --git a/clash-verge-rev/src/components/profile/profile-more.tsx b/clash-verge-rev/src/components/profile/profile-more.tsx deleted file mode 100644 index d28191b034..0000000000 --- a/clash-verge-rev/src/components/profile/profile-more.tsx +++ /dev/null @@ -1,261 +0,0 @@ -import { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useLockFn } from "ahooks"; -import { - Box, - Badge, - Chip, - Typography, - MenuItem, - Menu, - IconButton, -} from "@mui/material"; -import { FeaturedPlayListRounded } from "@mui/icons-material"; -import { viewProfile } from "@/services/cmds"; -import { Notice } from "@/components/base"; -import { EditorViewer } from "@/components/profile/editor-viewer"; -import { ProfileBox } from "./profile-box"; -import { LogViewer } from "./log-viewer"; -import { ConfirmViewer } from "./confirm-viewer"; - -interface Props { - selected: boolean; - itemData: IProfileItem; - enableNum: number; - logInfo?: [string, string][]; - onEnable: () => void; - onDisable: () => void; - onMoveTop: () => void; - onMoveEnd: () => void; - onDelete: () => void; - onEdit: () => void; -} - -// profile enhanced item -export const ProfileMore = (props: Props) => { - const { - selected, - itemData, - enableNum, - logInfo = [], - onEnable, - onDisable, - onMoveTop, - onMoveEnd, - onDelete, - onEdit, - } = props; - - const { uid, type } = itemData; - const { t, i18n } = useTranslation(); - const [anchorEl, setAnchorEl] = useState(null); - const [position, setPosition] = useState({ left: 0, top: 0 }); - const [fileOpen, setFileOpen] = useState(false); - const [confirmOpen, setConfirmOpen] = useState(false); - const [logOpen, setLogOpen] = useState(false); - - const onEditInfo = () => { - setAnchorEl(null); - onEdit(); - }; - - const onEditFile = () => { - setAnchorEl(null); - setFileOpen(true); - }; - - const onOpenFile = useLockFn(async () => { - setAnchorEl(null); - try { - await viewProfile(itemData.uid); - } catch (err: any) { - Notice.error(err?.message || err.toString()); - } - }); - - const fnWrapper = (fn: () => void) => () => { - setAnchorEl(null); - return fn(); - }; - - const hasError = !!logInfo.find((e) => e[0] === "exception"); - const showMove = enableNum > 1 && !hasError; - - const enableMenu = [ - { label: "Disable", handler: fnWrapper(onDisable) }, - { label: "Edit Info", handler: onEditInfo }, - { label: "Edit File", handler: onEditFile }, - { label: "Open File", handler: onOpenFile }, - { label: "To Top", show: showMove, handler: fnWrapper(onMoveTop) }, - { label: "To End", show: showMove, handler: fnWrapper(onMoveEnd) }, - { - label: "Delete", - handler: () => { - setAnchorEl(null); - setConfirmOpen(true); - }, - }, - ]; - - const disableMenu = [ - { label: "Enable", handler: fnWrapper(onEnable) }, - { label: "Edit Info", handler: onEditInfo }, - { label: "Edit File", handler: onEditFile }, - { label: "Open File", handler: onOpenFile }, - { - label: "Delete", - handler: () => { - setAnchorEl(null); - setConfirmOpen(true); - }, - }, - ]; - - const boxStyle = { - height: 26, - display: "flex", - alignItems: "center", - justifyContent: "space-between", - lineHeight: 1, - }; - - return ( - <> - onSelect(false)} - onContextMenu={(event) => { - const { clientX, clientY } = event; - setPosition({ top: clientY, left: clientX }); - setAnchorEl(event.currentTarget); - event.preventDefault(); - }} - > - - - {itemData.name} - - - - - - - {selected && type === "script" ? ( - hasError ? ( - - setLogOpen(true)} - > - - - - ) : ( - setLogOpen(true)} - > - - - ) - ) : ( - - {itemData.desc} - - )} - - - - setAnchorEl(null)} - anchorPosition={position} - anchorReference="anchorPosition" - transitionDuration={225} - MenuListProps={{ sx: { py: 0.5 } }} - onContextMenu={(e) => { - setAnchorEl(null); - e.preventDefault(); - }} - > - {(selected ? enableMenu : disableMenu) - .filter((item: any) => item.show !== false) - .map((item) => ( - { - return { - color: - item.label === "Delete" - ? theme.palette.error.main - : undefined, - }; - }, - ]} - dense - > - {t(item.label)} - - ))} - - - setFileOpen(false)} - /> - setConfirmOpen(false)} - onConfirm={() => { - onDelete(); - setConfirmOpen(false); - }} - /> - {selected && ( - setLogOpen(false)} - /> - )} - - ); -}; diff --git a/clash-verge-rev/src/components/profile/profile-viewer.tsx b/clash-verge-rev/src/components/profile/profile-viewer.tsx index 0a675231e9..06879d0dd7 100644 --- a/clash-verge-rev/src/components/profile/profile-viewer.tsx +++ b/clash-verge-rev/src/components/profile/profile-viewer.tsx @@ -33,7 +33,7 @@ export interface ProfileViewerRef { } // create or edit the profile -// remote / local / merge / script +// remote / local export const ProfileViewer = forwardRef( (props, ref) => { const { t } = useTranslation(); @@ -92,9 +92,6 @@ export const ProfileViewer = forwardRef( if (form.type === "remote" && !form.url) { throw new Error("The URL should not be null"); } - if (form.type !== "remote" && form.type !== "local") { - delete form.option; - } if (form.option?.update_interval) { form.option.update_interval = +form.option.update_interval; @@ -168,8 +165,6 @@ export const ProfileViewer = forwardRef( )} diff --git a/clash-verge-rev/src/components/setting/mods/sysproxy-viewer.tsx b/clash-verge-rev/src/components/setting/mods/sysproxy-viewer.tsx index b749220ac0..d5da90fb3a 100644 --- a/clash-verge-rev/src/components/setting/mods/sysproxy-viewer.tsx +++ b/clash-verge-rev/src/components/setting/mods/sysproxy-viewer.tsx @@ -249,10 +249,10 @@ export const SysproxyViewer = forwardRef((props, ref) => { property={value.pac_content ?? ""} open={editorOpen} language="javascript" - onChange={(content) => { + onChange={(_prev, curr) => { let pac = DEFAULT_PAC; - if (content && content.trim().length > 0) { - pac = content; + if (curr && curr.trim().length > 0) { + pac = curr; } setValue((v) => ({ ...v, pac_content: pac })); }} diff --git a/clash-verge-rev/src/components/setting/mods/theme-viewer.tsx b/clash-verge-rev/src/components/setting/mods/theme-viewer.tsx index c6ac7d4df9..fc74b25192 100644 --- a/clash-verge-rev/src/components/setting/mods/theme-viewer.tsx +++ b/clash-verge-rev/src/components/setting/mods/theme-viewer.tsx @@ -129,8 +129,8 @@ export const ThemeViewer = forwardRef((props, ref) => { property={theme.css_injection ?? ""} open={editorOpen} language="css" - onChange={(content) => { - theme.css_injection = content; + onChange={(_prev, curr) => { + theme.css_injection = curr; handleChange("css_injection"); }} onClose={() => { diff --git a/clash-verge-rev/src/hooks/use-log-data.ts b/clash-verge-rev/src/hooks/use-log-data.ts index cc3c50a5cb..4debdcc0ed 100644 --- a/clash-verge-rev/src/hooks/use-log-data.ts +++ b/clash-verge-rev/src/hooks/use-log-data.ts @@ -50,7 +50,7 @@ export const useLogData = () => { }; }, { - fallbackData: { up: 0, down: 0 }, + fallbackData: [], keepPreviousData: true, } ); diff --git a/clash-verge-rev/src/locales/en.json b/clash-verge-rev/src/locales/en.json index 604bc78cb7..a8e3679cc0 100644 --- a/clash-verge-rev/src/locales/en.json +++ b/clash-verge-rev/src/locales/en.json @@ -1,20 +1,17 @@ { "millis": "millis", "mins": "mins", - "Back": "Back", "Close": "Close", "Cancel": "Cancel", "Confirm": "Confirm", "Empty": "Empty", - "New": "New", "Edit": "Edit", "Save": "Save", "Delete": "Delete", "Enable": "Enable", "Disable": "Disable", - "Label-Proxies": "Proxies", "Label-Profiles": "Profiles", "Label-Connections": "Connections", @@ -22,7 +19,6 @@ "Label-Logs": "Logs", "Label-Test": "Test", "Label-Settings": "Settings", - "Proxies": "Proxies", "Proxy Groups": "Proxy Groups", "Proxy Provider": "Proxy Provider", @@ -41,7 +37,6 @@ "Delay check to cancel fixed": "Delay check to cancel fixed", "Proxy basic": "Proxy basic", "Proxy detail": "Proxy detail", - "Profiles": "Profiles", "Update All Profiles": "Update All Profiles", "View Runtime Config": "View Runtime Config", @@ -49,8 +44,17 @@ "Paste": "Paste", "Profile URL": "Profile URL", "Import": "Import", + "From": "From", + "Update Time": "Update Time", + "Used / Total": "Used / Total", + "Expire Time": "Expire Time", "Create Profile": "Create Profile", "Edit Profile": "Edit Profile", + "Edit Proxies": "Edit Proxies", + "Edit Rules": "Edit Rules", + "Edit Groups": "Edit Proxy Groups", + "Edit Merge": "Edit Merge", + "Edit Script": "Edit Script", "Type": "Type", "Name": "Name", "Descriptions": "Descriptions", @@ -73,7 +77,6 @@ "Script Console": "Script Console", "To Top": "To Top", "To End": "To End", - "Connections": "Connections", "Table View": "Table View", "List View": "List View", @@ -93,21 +96,17 @@ "Source": "Source", "Destination IP": "Destination IP", "Close Connection": "Close Connection", - "Rules": "Rules", "Rule Provider": "Rule Provider", - "Logs": "Logs", "Pause": "Pause", "Clear": "Clear", - "Test": "Test", "Test All": "Test All", "Create Test": "Create Test", "Edit Test": "Edit Test", "Icon": "Icon", "Test URL": "Test URL", - "Settings": "Settings", "System Setting": "System Setting", "Tun Mode": "Tun Mode", @@ -153,7 +152,6 @@ "Auto Launch": "Auto Launch", "Silent Start": "Silent Start", "Silent Start Info": "Start the program in background mode without displaying the panel", - "Clash Setting": "Clash Setting", "Allow Lan": "Allow Lan", "IPv6": "IPv6", @@ -177,7 +175,9 @@ "Open UWP tool": "Open UWP tool", "Open UWP tool Info": "Since Windows 8, UWP apps (such as Microsoft Store) are restricted from directly accessing local host network services, and this tool can be used to bypass this restriction", "Update GeoData": "Update GeoData", - + "TG Channel": "Telegram Channel", + "Manual": "Manual", + "Github Repo": "Github Repo", "Verge Setting": "Verge Setting", "Language": "Language", "Theme Mode": "Theme Mode", @@ -246,7 +246,6 @@ "Open Dev Tools": "Open Dev Tools", "Exit": "Exit", "Verge Version": "Verge Version", - "ReadOnly": "ReadOnly", "ReadOnlyMessage": "Cannot edit in read-only editor", "Filter": "Filter", @@ -254,7 +253,6 @@ "Match Case": "Match Case", "Match Whole Word": "Match Whole Word", "Use Regular Expression": "Use Regular Expression", - "Profile Imported Successfully": "Profile Imported Successfully", "Clash Config Updated": "Clash Config Updated", "Profile Switched": "Profile Switched", diff --git a/clash-verge-rev/src/locales/fa.json b/clash-verge-rev/src/locales/fa.json index 6acc9df02c..c9bea1f911 100644 --- a/clash-verge-rev/src/locales/fa.json +++ b/clash-verge-rev/src/locales/fa.json @@ -1,20 +1,17 @@ { "millis": "میلی‌ثانیه", "mins": "دقیقه", - "Back": "بازگشت", "Close": "بستن", "Cancel": "لغو", "Confirm": "تأیید", "Empty": "خالی خالی", - "New": "جدید", "Edit": "ویرایش", "Save": "ذخیره", "Delete": "حذف", "Enable": "فعال کردن", "Disable": "غیرفعال کردن", - "Label-Proxies": "پراکسی‌ها", "Label-Profiles": "پروفایل‌ها", "Label-Connections": "اتصالات", @@ -22,7 +19,6 @@ "Label-Logs": "لاگ‌ها", "Label-Test": "آزمون", "Label-Settings": "تنظیمات", - "Proxies": "پراکسی‌ها", "Proxy Groups": "گروه‌های پراکسی", "Proxy Provider": "تأمین‌کننده پروکسی", @@ -41,7 +37,6 @@ "Delay check to cancel fixed": "بررسی تأخیر برای لغو ثابت", "Proxy basic": "پراکسی پایه", "Proxy detail": "جزئیات پراکسی", - "Profiles": "پروفایل‌ها", "Update All Profiles": "به‌روزرسانی همه پروفایل‌ها", "View Runtime Config": "مشاهده پیکربندی زمان اجرا", @@ -49,8 +44,14 @@ "Paste": "چسباندن", "Profile URL": "آدرس پروفایل", "Import": "وارد کردن", + "From": "از", + "Update Time": "زمان به‌روزرسانی", + "Used / Total": "استفاده‌شده / کل", + "Expire Time": "زمان انقضا", "Create Profile": "ایجاد پروفایل", "Edit Profile": "ویرایش پروفایل", + "Edit Merge": "ادغام ویرایش", + "Edit Script": "ویرایش اسکریپت", "Type": "نوع", "Name": "نام", "Descriptions": "توضیحات", @@ -73,7 +74,6 @@ "Script Console": "کنسول اسکریپت", "To Top": "به بالا", "To End": "به پایان", - "Connections": "اتصالات", "Table View": "نمای جدولی", "List View": "نمای لیستی", @@ -93,21 +93,17 @@ "Source": "منبع", "Destination IP": "آدرس IP مقصد", "Close Connection": "بستن اتصال", - "Rules": "قوانین", "Rule Provider": "تأمین‌کننده قانون", - "Logs": "لاگ‌ها", "Pause": "توقف", "Clear": "پاک کردن", - "Test": "آزمون", "Test All": "آزمون همه", "Create Test": "ایجاد آزمون", "Edit Test": "ویرایش آزمون", "Icon": "آیکون", "Test URL": "آدرس آزمون", - "Settings": "تنظیمات", "System Setting": "تنظیمات سیستم", "Tun Mode": "حالت Tun", @@ -153,7 +149,6 @@ "Auto Launch": "راه‌اندازی خودکار", "Silent Start": "شروع بی‌صدا", "Silent Start Info": "برنامه را در حالت پس‌زمینه بدون نمایش پانل اجرا کنید", - "Clash Setting": "تنظیمات Clash", "Allow Lan": "اجازه LAN", "IPv6": "IPv6", @@ -182,7 +177,9 @@ "Open UWP tool": "باز کردن ابزار UWP", "Open UWP tool Info": "از ویندوز 8 به بعد، برنامه‌های UWP (مانند Microsoft Store) از دسترسی مستقیم به خدمات شبکه محلی محدود شده‌اند و این ابزار می‌تواند برای دور زدن این محدودیت استفاده شود", "Update GeoData": "به‌روزرسانی GeoData", - + "TG Channel": "کانال تلگرام", + "Manual": "راهنما", + "Github Repo": "مخزن GitHub", "Verge Setting": "تنظیمات Verge", "Language": "زبان", "Theme Mode": "حالت تم", @@ -251,7 +248,6 @@ "Open Dev Tools": "باز کردن ابزارهای توسعه‌دهنده", "Exit": "خروج", "Verge Version": "نسخه Verge", - "ReadOnly": "فقط خواندنی", "ReadOnlyMessage": "نمی‌توان در ویرایشگر فقط خواندنی ویرایش کرد", "Filter": "فیلتر", @@ -259,7 +255,6 @@ "Match Case": "تطبیق حروف کوچک و بزرگ", "Match Whole Word": "تطبیق کل کلمه", "Use Regular Expression": "استفاده از عبارت منظم", - "Profile Imported Successfully": "پروفایل با موفقیت وارد شد", "Clash Config Updated": "پیکربندی Clash به‌روزرسانی شد", "Profile Switched": "پروفایل تغییر یافت", diff --git a/clash-verge-rev/src/locales/ru.json b/clash-verge-rev/src/locales/ru.json index 432170a349..dee452cd5b 100644 --- a/clash-verge-rev/src/locales/ru.json +++ b/clash-verge-rev/src/locales/ru.json @@ -1,20 +1,17 @@ { "millis": "миллисекунды", "mins": "минуты", - "Back": "Назад", "Close": "Закрыть", "Cancel": "Отмена", "Confirm": "Подтвердить", "Empty": "Пусто", - "New": "Новый", "Edit": "Редактировать", "Save": "Сохранить", "Delete": "Удалить", "Enable": "Включить", "Disable": "Отключить", - "Label-Proxies": "Прокси", "Label-Profiles": "Профили", "Label-Connections": "Соединения", @@ -22,7 +19,6 @@ "Label-Logs": "Логи", "Label-Test": "Тест", "Label-Settings": "Настройки", - "Proxies": "Прокси", "Proxy Groups": "Группы прокси", "Proxy Provider": "Провайдер прокси", @@ -41,7 +37,6 @@ "Delay check to cancel fixed": "Проверка задержки для отмены фиксированного", "Proxy basic": "Резюме о прокси", "Proxy detail": "Подробности о прокси", - "Profiles": "Профили", "Update All Profiles": "Обновить все профили", "View Runtime Config": "Просмотреть используемый конфиг", @@ -49,8 +44,14 @@ "Paste": "Вставить", "Profile URL": "URL профиля", "Import": "Импорт", + "From": "От", + "Update Time": "Время обновления", + "Used / Total": "Использовано / Всего", + "Expire Time": "Время окончания", "Create Profile": "Создать профиль", "Edit Profile": "Изменить профиль", + "Edit Merge": "Изменить Merge.", + "Edit Script": "Изменить Script", "Type": "Тип", "Name": "Название", "Descriptions": "Описания", @@ -73,7 +74,6 @@ "Script Console": "Консоль скрипта", "To Top": "Наверх", "To End": "Вниз", - "Connections": "Соединения", "Table View": "Tablichnyy vid", "List View": "Spiskovyy vid", @@ -93,21 +93,17 @@ "Source": "Исходный адрес", "Destination IP": "IP-адрес назначения", "Close Connection": "Закрыть соединение", - "Rules": "Правила", "Rule Provider": "Провайдер правило", - "Logs": "Логи", "Pause": "Пауза", "Clear": "Очистить", - "Test": "Тест", "Test All": "Тест Все", "Create Test": "Создать тест", "Edit Test": "Редактировать тест", "Icon": "Икона", "Test URL": "Тестовый URL", - "Settings": "Настройки", "System Setting": "Настройки системы", "Tun Mode": "Режим туннеля", @@ -153,7 +149,6 @@ "Auto Launch": "Автозапуск", "Silent Start": "Тихий запуск", "Silent Start Info": "Запускать программу в фоновом режиме без отображения панели", - "Clash Setting": "Настройки Clash", "Allow Lan": "Разрешить локальную сеть", "IPv6": "IPv6", @@ -182,7 +177,9 @@ "Open UWP tool": "Открыть UWP инструмент", "Open UWP tool Info": "С Windows 8 приложения UWP (такие как Microsoft Store) ограничены в прямом доступе к сетевым службам локального хоста, и этот инструмент позволяет обойти это ограничение", "Update GeoData": "Обновление GeoData", - + "TG Channel": "Канал Telegram", + "Manual": "Документация", + "Github Repo": "GitHub репозиторий", "Verge Setting": "Настройки Verge", "Language": "Язык", "Theme Mode": "Режим темы", @@ -251,7 +248,6 @@ "Open Dev Tools": "Открыть инструменты разработчика", "Exit": "Выход", "Verge Version": "Версия Verge", - "ReadOnly": "Только для чтения", "ReadOnlyMessage": "Невозможно редактировать в режиме только для чтения", "Filter": "Фильтр", @@ -259,7 +255,6 @@ "Match Case": "Учитывать регистр", "Match Whole Word": "Полное совпадение слова", "Use Regular Expression": "Использовать регулярные выражения", - "Profile Imported Successfully": "Профиль успешно импортирован", "Clash Config Updated": "Clash конфигурация Обновлена", "Profile Switched": "Профиль изменен", diff --git a/clash-verge-rev/src/locales/zh.json b/clash-verge-rev/src/locales/zh.json index 2c7bd4c46b..b9d7777d17 100644 --- a/clash-verge-rev/src/locales/zh.json +++ b/clash-verge-rev/src/locales/zh.json @@ -1,20 +1,17 @@ { "millis": "毫秒", "mins": "分钟", - "Back": "返回", "Close": "关闭", "Cancel": "取消", "Confirm": "确认", "Empty": "空空如也", - "New": "新建", "Edit": "编辑", "Save": "保存", "Delete": "删除", "Enable": "启用", "Disable": "禁用", - "Label-Proxies": "代 理", "Label-Profiles": "订 阅", "Label-Connections": "连 接", @@ -22,7 +19,6 @@ "Label-Logs": "日 志", "Label-Test": "测 试", "Label-Settings": "设 置", - "Proxies": "代理", "Proxy Groups": "代理组", "Proxy Provider": "代理集合", @@ -41,7 +37,6 @@ "Delay check to cancel fixed": "进行延迟测试,以取消固定", "Proxy basic": "隐藏节点细节", "Proxy detail": "展示节点细节", - "Profiles": "订阅", "Update All Profiles": "更新所有订阅", "View Runtime Config": "查看运行时订阅", @@ -49,8 +44,17 @@ "Paste": "粘贴", "Profile URL": "订阅文件链接", "Import": "导入", + "From": "来自", + "Update Time": "更新时间", + "Used / Total": "已使用 / 总量", + "Expire Time": "到期时间", "Create Profile": "新建配置", "Edit Profile": "编辑配置", + "Edit Proxies": "编辑代理", + "Edit Rules": "编辑规则", + "Edit Groups": "编辑代理组", + "Edit Merge": "编辑 Merge", + "Edit Script": "编辑 Script", "Type": "类型", "Name": "名称", "Descriptions": "描述", @@ -73,7 +77,6 @@ "Script Console": "脚本控制台输出", "To Top": "移到最前", "To End": "移到末尾", - "Connections": "连接", "Table View": "表格视图", "List View": "列表视图", @@ -93,21 +96,17 @@ "Source": "源地址", "Destination IP": "目标地址", "Close Connection": "关闭连接", - "Rules": "规则", "Rule Provider": "规则集合", - "Logs": "日志", "Pause": "暂停", "Clear": "清除", - "Test": "测试", "Test All": "测试全部", "Create Test": "新建测试", "Edit Test": "编辑测试", "Icon": "图标", "Test URL": "测试地址", - "Settings": "设置", "System Setting": "系统设置", "Tun Mode": "Tun 模式", @@ -153,7 +152,9 @@ "Auto Launch": "开机自启", "Silent Start": "静默启动", "Silent Start Info": "程序启动时以后台模式运行,不显示程序面板", - + "TG Channel": "Telegram 频道", + "Manual": "使用手册", + "Github Repo": "GitHub 项目地址", "Clash Setting": "Clash 设置", "Allow Lan": "局域网连接", "IPv6": "IPv6", @@ -176,13 +177,12 @@ "Upgrade": "升级内核", "Restart": "重启内核", "Release Version": "正式版", - "Alpha Version": "内测版", + "Alpha Version": "测试版", "Tun mode requires": "如需启用 Tun 模式需要授权", "Grant": "授权", "Open UWP tool": "UWP 工具", "Open UWP tool Info": "Windows 8开始限制 UWP 应用(如微软商店)直接访问本地主机的网络服务,使用此工具可绕过该限制", "Update GeoData": "更新 GeoData", - "Verge Setting": "Verge 设置", "Language": "语言设置", "Theme Mode": "主题模式", @@ -251,7 +251,6 @@ "Open Dev Tools": "打开开发者工具", "Exit": "退出", "Verge Version": "Verge 版本", - "ReadOnly": "只读", "ReadOnlyMessage": "无法在只读模式下编辑", "Filter": "过滤节点", @@ -259,7 +258,6 @@ "Match Case": "区分大小写", "Match Whole Word": "全字匹配", "Use Regular Expression": "使用正则表达式", - "Profile Imported Successfully": "导入订阅成功", "Clash Config Updated": "Clash 配置已更新", "Profile Switched": "订阅已切换", diff --git a/clash-verge-rev/src/pages/profiles.tsx b/clash-verge-rev/src/pages/profiles.tsx index be92cc3f3e..9c641e6404 100644 --- a/clash-verge-rev/src/pages/profiles.tsx +++ b/clash-verge-rev/src/pages/profiles.tsx @@ -42,7 +42,6 @@ import { ProfileViewerRef, } from "@/components/profile/profile-viewer"; import { ProfileItem } from "@/components/profile/profile-item"; -import { ProfileMore } from "@/components/profile/profile-more"; import { useProfiles } from "@/hooks/use-profiles"; import { ConfigViewer } from "@/components/setting/mods/config-viewer"; import { throttle } from "lodash-es"; @@ -56,7 +55,7 @@ const ProfilePage = () => { const [url, setUrl] = useState(""); const [disabled, setDisabled] = useState(false); - const [activating, setActivating] = useState(""); + const [activatings, setActivatings] = useState([]); const [loading, setLoading] = useState(false); const sensors = useSensors( useSensor(PointerSensor), @@ -105,29 +104,24 @@ const ProfilePage = () => { getRuntimeLogs ); - const chain = profiles.chain || []; const viewerRef = useRef(null); const configRef = useRef(null); // distinguish type - const { regularItems, enhanceItems } = useMemo(() => { + const profileItems = useMemo(() => { const items = profiles.items || []; - const chain = profiles.chain || []; const type1 = ["local", "remote"]; - const type2 = ["merge", "script"]; - const regularItems = items.filter((i) => i && type1.includes(i.type!)); - const restItems = items.filter((i) => i && type2.includes(i.type!)); - const restMap = Object.fromEntries(restItems.map((i) => [i.uid, i])); - const enhanceItems = chain - .map((i) => restMap[i]!) - .filter(Boolean) - .concat(restItems.filter((i) => !chain.includes(i.uid))); + const profileItems = items.filter((i) => i && type1.includes(i.type!)); - return { regularItems, enhanceItems }; + return profileItems; }, [profiles]); + const currentActivatings = () => { + return [...new Set([profiles.current ?? ""])].filter(Boolean); + }; + const onImport = async () => { if (!url) return; setLoading(true); @@ -138,13 +132,13 @@ const ProfilePage = () => { setUrl(""); setLoading(false); - getProfiles().then((newProfiles) => { + getProfiles().then(async (newProfiles) => { mutate("getProfiles", newProfiles); const remoteItem = newProfiles.items?.find((e) => e.type === "remote"); if (!newProfiles.current && remoteItem) { const current = remoteItem.uid; - patchProfiles({ current }); + await patchProfiles({ current }); mutateLogs(); setTimeout(() => activateSelected(), 2000); } @@ -171,7 +165,9 @@ const ProfilePage = () => { const onSelect = useLockFn(async (current: string, force: boolean) => { if (!force && current === profiles.current) return; // 避免大多数情况下loading态闪烁 - const reset = setTimeout(() => setActivating(current), 100); + const reset = setTimeout(() => { + setActivatings([...currentActivatings(), current]); + }, 100); try { await patchProfiles({ current }); mutateLogs(); @@ -182,59 +178,38 @@ const ProfilePage = () => { Notice.error(err?.message || err.toString(), 4000); } finally { clearTimeout(reset); - setActivating(""); + setActivatings([]); } }); const onEnhance = useLockFn(async () => { + setActivatings(currentActivatings()); try { await enhanceProfiles(); mutateLogs(); Notice.success(t("Profile Reactivated"), 1000); } catch (err: any) { Notice.error(err.message || err.toString(), 3000); + } finally { + setActivatings([]); } }); - const onEnable = useLockFn(async (uid: string) => { - if (chain.includes(uid)) return; - const newChain = [...chain, uid]; - await patchProfiles({ chain: newChain }); - mutateLogs(); - }); - - const onDisable = useLockFn(async (uid: string) => { - if (!chain.includes(uid)) return; - const newChain = chain.filter((i) => i !== uid); - await patchProfiles({ chain: newChain }); - mutateLogs(); - }); - const onDelete = useLockFn(async (uid: string) => { + const current = profiles.current === uid; try { - await onDisable(uid); + setActivatings([...(current ? currentActivatings() : []), uid]); await deleteProfile(uid); mutateProfiles(); mutateLogs(); + current && (await onEnhance()); } catch (err: any) { Notice.error(err?.message || err.toString()); + } finally { + setActivatings([]); } }); - const onMoveTop = useLockFn(async (uid: string) => { - if (!chain.includes(uid)) return; - const newChain = [uid].concat(chain.filter((i) => i !== uid)); - await patchProfiles({ chain: newChain }); - mutateLogs(); - }); - - const onMoveEnd = useLockFn(async (uid: string) => { - if (!chain.includes(uid)) return; - const newChain = chain.filter((i) => i !== uid).concat([uid]); - await patchProfiles({ chain: newChain }); - mutateLogs(); - }); - // 更新所有订阅 const setLoadingCache = useSetLoadingCache(); const onUpdateAll = useLockFn(async () => { @@ -253,7 +228,7 @@ const ProfilePage = () => { return new Promise((resolve) => { setLoadingCache((cache) => { // 获取没有正在更新的订阅 - const items = regularItems.filter( + const items = profileItems.filter( (e) => e.type === "remote" && !cache[e.uid] ); const change = Object.fromEntries(items.map((e) => [e.uid, true])); @@ -268,11 +243,6 @@ const ProfilePage = () => { const text = await readText(); if (text) setUrl(text); }; - const mode = useThemeMode(); - const islight = mode === "light" ? true : false; - const dividercolor = islight - ? "rgba(0, 0, 0, 0.06)" - : "rgba(255, 255, 255, 0.06)"; return ( { { + items={profileItems.map((x) => { return x.uid; })} > - {regularItems.map((item) => ( + {profileItems.map((item) => ( onSelect(item.uid, f)} onEdit={() => viewerRef.current?.edit(item)} + onChange={async (prev, curr) => { + if (prev !== curr && profiles.current === item.uid) { + await onEnhance(); + } + }} + onDelete={() => onDelete(item.uid)} /> ))} @@ -407,37 +383,6 @@ const ProfilePage = () => { - - {enhanceItems.length > 0 && ( - - )} - - {enhanceItems.length > 0 && ( - - - {enhanceItems.map((item) => ( - - onEnable(item.uid)} - onDisable={() => onDisable(item.uid)} - onDelete={() => onDelete(item.uid)} - onMoveTop={() => onMoveTop(item.uid)} - onMoveEnd={() => onMoveEnd(item.uid)} - onEdit={() => viewerRef.current?.edit(item)} - /> - - ))} - - - )} mutateProfiles()} /> diff --git a/clash-verge-rev/src/pages/settings.tsx b/clash-verge-rev/src/pages/settings.tsx index adf7538a46..d3b6064f77 100644 --- a/clash-verge-rev/src/pages/settings.tsx +++ b/clash-verge-rev/src/pages/settings.tsx @@ -2,7 +2,7 @@ import { Box, ButtonGroup, Grid, IconButton } from "@mui/material"; import { useLockFn } from "ahooks"; import { useTranslation } from "react-i18next"; import { BasePage, Notice } from "@/components/base"; -import { GitHub, HelpOutlineSharp } from "@mui/icons-material"; +import { GitHub, HelpOutlineSharp, Telegram } from "@mui/icons-material"; import { openWebUrl } from "@/services/cmds"; import SettingVerge from "@/components/setting/setting-verge"; import SettingClash from "@/components/setting/setting-clash"; @@ -21,7 +21,11 @@ const SettingPage = () => { }); const toGithubDoc = useLockFn(() => { - return openWebUrl("https://clash-verge-rev.github.io/guide/log.html"); + return openWebUrl("https://clash-verge-rev.github.io/index.html"); + }); + + const toTelegramChannel = useLockFn(() => { + return openWebUrl("https://t.me/clash_verge_re"); }); const mode = useThemeMode(); @@ -35,7 +39,7 @@ const SettingPage = () => { @@ -43,7 +47,16 @@ const SettingPage = () => { + + + + diff --git a/clash-verge-rev/src/services/types.d.ts b/clash-verge-rev/src/services/types.d.ts index 13734526fd..9cdc4cb117 100644 --- a/clash-verge-rev/src/services/types.d.ts +++ b/clash-verge-rev/src/services/types.d.ts @@ -178,11 +178,15 @@ interface IProfileOption { self_proxy?: boolean; update_interval?: number; danger_accept_invalid_certs?: boolean; + merge?: string; + script?: string; + rules?: string; + proxies?: string; + groups?: string; } interface IProfilesConfig { current?: string; - chain?: string[]; valid?: string[]; items?: IProfileItem[]; } @@ -254,75 +258,3 @@ interface IVergeConfig { proxy_layout_column?: number; test_list?: IVergeTestItem[]; } - -type IClashConfigValue = any; - -interface IProfileMerge { - // clash config fields (default supports) - rules?: IClashConfigValue; - proxies?: IClashConfigValue; - "proxy-groups"?: IClashConfigValue; - "proxy-providers"?: IClashConfigValue; - "rule-providers"?: IClashConfigValue; - // clash config fields (use flag) - tun?: IClashConfigValue; - dns?: IClashConfigValue; - hosts?: IClashConfigValue; - script?: IClashConfigValue; - profile?: IClashConfigValue; - payload?: IClashConfigValue; - "interface-name"?: IClashConfigValue; - "routing-mark"?: IClashConfigValue; - // functional fields - use?: string[]; - "prepend-rules"?: any[]; - "append-rules"?: any[]; - "prepend-proxies"?: any[]; - "append-proxies"?: any[]; - "prepend-proxy-groups"?: any[]; - "append-proxy-groups"?: any[]; - // fix - ebpf?: any; - experimental?: any; - iptables?: any; - sniffer?: any; - authentication?: any; - "bind-address"?: any; - "external-ui"?: any; - "auto-redir"?: any; - "socks-port"?: any; - "redir-port"?: any; - "tproxy-port"?: any; - "geodata-mode"?: any; - "tcp-concurrent"?: any; -} - -// partial of the clash config -type IProfileData = Partial<{ - rules: any[]; - proxies: any[]; - "proxy-groups": any[]; - "proxy-providers": any[]; - "rule-providers": any[]; - - [k: string]: any; -}>; - -interface IChainItem { - item: IProfileItem; - merge?: IProfileMerge; - script?: string; -} - -interface IEnhancedPayload { - chain: IChainItem[]; - valid: string[]; - current: IProfileData; - callback: string; -} - -interface IEnhancedResult { - data: IProfileData; - status: string; - error?: string; -} diff --git a/clashn/README.md b/clashn/README.md index 1cc7400ec3..c41dc74be2 100644 --- a/clashn/README.md +++ b/clashn/README.md @@ -1,3 +1,6 @@ +## [本项目功能将合并到v2rayN项目](https://github.com/2dust/clashN/issues/358) + + # ClashN A clash client for Windows, supports [Mihomo core](https://github.com/MetaCubeX/Mihomo) diff --git a/echo/internal/constant/constant.go b/echo/internal/constant/constant.go index c85ab00f2c..1eebadb92f 100644 --- a/echo/internal/constant/constant.go +++ b/echo/internal/constant/constant.go @@ -8,7 +8,7 @@ var ( // allow change in test IdleTimeOut = 10 * time.Second - Version = "1.1.4" + Version = "1.1.5-dev" GitBranch string GitRevision string BuildTime string diff --git a/echo/internal/web/server.go b/echo/internal/web/server.go index 575bf90250..3e64fc2a3f 100644 --- a/echo/internal/web/server.go +++ b/echo/internal/web/server.go @@ -48,7 +48,8 @@ func NewServer( cfg *config.Config, relayReloader glue.Reloader, healthChecker glue.HealthChecker, - connMgr cmgr.Cmgr) (*Server, error) { + connMgr cmgr.Cmgr, +) (*Server, error) { l := zap.S().Named("web") templates := template.Must(template.ParseFS(templatesFS, "templates/*.html")) diff --git a/echo/pkg/http/http.go b/echo/pkg/http/http.go index e74ed3611a..be0d8deb71 100644 --- a/echo/pkg/http/http.go +++ b/echo/pkg/http/http.go @@ -5,12 +5,19 @@ import ( "encoding/json" "io" + "github.com/Ehco1996/ehco/pkg/log" "github.com/hashicorp/go-retryablehttp" ) -func PostJSONWithRetry(url string, dataStruct interface{}) error { +func generateRetirableClient() *retryablehttp.Client { retryClient := retryablehttp.NewClient() retryClient.RetryMax = 3 + retryClient.Logger = log.NewZapLeveledLogger("http") + return retryClient +} + +func PostJSONWithRetry(url string, dataStruct interface{}) error { + retryClient := generateRetirableClient() buf := new(bytes.Buffer) if err := json.NewEncoder(buf).Encode(dataStruct); err != nil { @@ -26,10 +33,8 @@ func PostJSONWithRetry(url string, dataStruct interface{}) error { } func GetJSONWithRetry(url string, dataStruct interface{}) error { - retryClient := retryablehttp.NewClient() - retryClient.RetryMax = 3 - - resp, err := retryablehttp.Get(url) + retryClient := generateRetirableClient() + resp, err := retryClient.Get(url) if err != nil { return err } diff --git a/echo/pkg/log/leveld.go b/echo/pkg/log/leveld.go new file mode 100644 index 0000000000..bdacb30f44 --- /dev/null +++ b/echo/pkg/log/leveld.go @@ -0,0 +1,37 @@ +package log + +import ( + "github.com/hashicorp/go-retryablehttp" + "go.uber.org/zap" +) + +// ZapLeveledLogger wrapper for zap.logger for use with hashicorp's go-retryable LeveledLogger +// port from https://github.com/hashicorp/go-retryablehttp/issues/182#issuecomment-1758011585 +type ZapLeveledLogger struct { + Logger *zap.SugaredLogger +} + +// New creates a ZapLeveledLogger with a zap.logger that satisfies standard library log.Logger interface. +func NewZapLeveledLogger(name string) retryablehttp.LeveledLogger { + if globalInitd { + return &ZapLeveledLogger{Logger: zap.S().Named(name)} + } + logger, _ := initLogger("info", false) + return &ZapLeveledLogger{Logger: logger.Sugar().Named(name)} +} + +func (l *ZapLeveledLogger) Error(msg string, keysAndValues ...interface{}) { + l.Logger.Errorw(msg, keysAndValues...) +} + +func (l *ZapLeveledLogger) Info(msg string, keysAndValues ...interface{}) { + l.Logger.Infow(msg, keysAndValues...) +} + +func (l *ZapLeveledLogger) Debug(msg string, keysAndValues ...interface{}) { + l.Logger.Debugw(msg, keysAndValues...) +} + +func (l *ZapLeveledLogger) Warn(msg string, keysAndValues ...interface{}) { + l.Logger.Warnw(msg, keysAndValues...) +} diff --git a/echo/pkg/log/log.go b/echo/pkg/log/log.go index bd9469ba0f..ca76e3d1ae 100644 --- a/echo/pkg/log/log.go +++ b/echo/pkg/log/log.go @@ -8,7 +8,10 @@ import ( "go.uber.org/zap/zapcore" ) -var doOnce sync.Once +var ( + doOnce sync.Once + globalInitd bool +) func initLogger(logLevel string, replaceGlobal bool) (*zap.Logger, error) { level := zapcore.InfoLevel @@ -41,6 +44,7 @@ func InitGlobalLogger(logLevel string) error { var err error doOnce.Do(func() { _, err = initLogger(logLevel, true) + globalInitd = true }) return err } diff --git a/filebrowser/go.mod b/filebrowser/go.mod index 18bd58957c..1219a74235 100644 --- a/filebrowser/go.mod +++ b/filebrowser/go.mod @@ -25,8 +25,8 @@ require ( github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce go.etcd.io/bbolt v1.3.9 golang.org/x/crypto v0.21.0 - golang.org/x/image v0.15.0 - golang.org/x/text v0.14.0 + golang.org/x/image v0.18.0 + golang.org/x/text v0.16.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/filebrowser/go.sum b/filebrowser/go.sum index 8287feffe4..33bef8a6d0 100644 --- a/filebrowser/go.sum +++ b/filebrowser/go.sum @@ -192,8 +192,8 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= -golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= +golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -205,8 +205,8 @@ golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -225,8 +225,8 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= diff --git a/lede/target/linux/generic/backport-6.6/752-26-v6.10-net-ethernet-mtk_eth_soc-ppe-add-support-for-multipl.patch b/lede/target/linux/generic/backport-6.6/752-26-v6.10-net-ethernet-mtk_eth_soc-ppe-add-support-for-multipl.patch new file mode 100644 index 0000000000..07e7e86340 --- /dev/null +++ b/lede/target/linux/generic/backport-6.6/752-26-v6.10-net-ethernet-mtk_eth_soc-ppe-add-support-for-multipl.patch @@ -0,0 +1,371 @@ +From dee4dd10c79aaca192b73520d8fb64628468ae0f Mon Sep 17 00:00:00 2001 +From: Elad Yifee +Date: Fri, 7 Jun 2024 11:21:50 +0300 +Subject: [PATCH] net: ethernet: mtk_eth_soc: ppe: add support for multiple + PPEs + +Add the missing pieces to allow multiple PPEs units, one for each GMAC. +mtk_gdm_config has been modified to work on targted mac ID, +the inner loop moved outside of the function to allow unrelated +operations like setting the MAC's PPE index. +Introduce a sanity check in flow_offload_replace to account for +non-MTK ingress devices. +Additional field 'ppe_idx' was added to struct mtk_mac in order +to keep track on the assigned PPE unit. + +Signed-off-by: Elad Yifee +Link: https://lore.kernel.org/r/20240607082155.20021-1-eladwf@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/ethernet/mediatek/mtk_eth_soc.c | 112 +++++++++++------- + drivers/net/ethernet/mediatek/mtk_eth_soc.h | 8 +- + .../net/ethernet/mediatek/mtk_ppe_offload.c | 17 ++- + 3 files changed, 92 insertions(+), 45 deletions(-) + +--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c ++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c +@@ -80,7 +80,9 @@ static const struct mtk_reg_map mtk_reg_ + .fq_blen = 0x1b2c, + }, + .gdm1_cnt = 0x2400, +- .gdma_to_ppe = 0x4444, ++ .gdma_to_ppe = { ++ [0] = 0x4444, ++ }, + .ppe_base = 0x0c00, + .wdma_base = { + [0] = 0x2800, +@@ -144,7 +146,10 @@ static const struct mtk_reg_map mt7986_r + .tx_sch_rate = 0x4798, + }, + .gdm1_cnt = 0x1c00, +- .gdma_to_ppe = 0x3333, ++ .gdma_to_ppe = { ++ [0] = 0x3333, ++ [1] = 0x4444, ++ }, + .ppe_base = 0x2000, + .wdma_base = { + [0] = 0x4800, +@@ -192,7 +197,11 @@ static const struct mtk_reg_map mt7988_r + .tx_sch_rate = 0x4798, + }, + .gdm1_cnt = 0x1c00, +- .gdma_to_ppe = 0x3333, ++ .gdma_to_ppe = { ++ [0] = 0x3333, ++ [1] = 0x4444, ++ [2] = 0xcccc, ++ }, + .ppe_base = 0x2000, + .wdma_base = { + [0] = 0x4800, +@@ -2015,6 +2024,7 @@ static int mtk_poll_rx(struct napi_struc + struct mtk_rx_dma_v2 *rxd, trxd; + int done = 0, bytes = 0; + dma_addr_t dma_addr = DMA_MAPPING_ERROR; ++ int ppe_idx = 0; + + while (done < budget) { + unsigned int pktlen, *rxdcsum; +@@ -2058,6 +2068,7 @@ static int mtk_poll_rx(struct napi_struc + goto release_desc; + + netdev = eth->netdev[mac]; ++ ppe_idx = eth->mac[mac]->ppe_idx; + + if (unlikely(test_bit(MTK_RESETTING, ð->state))) + goto release_desc; +@@ -2181,7 +2192,7 @@ static int mtk_poll_rx(struct napi_struc + } + + if (reason == MTK_PPE_CPU_REASON_HIT_UNBIND_RATE_REACHED) +- mtk_ppe_check_skb(eth->ppe[0], skb, hash); ++ mtk_ppe_check_skb(eth->ppe[ppe_idx], skb, hash); + + skb_record_rx_queue(skb, 0); + napi_gro_receive(napi, skb); +@@ -3276,37 +3287,27 @@ static int mtk_start_dma(struct mtk_eth + return 0; + } + +-static void mtk_gdm_config(struct mtk_eth *eth, u32 config) ++static void mtk_gdm_config(struct mtk_eth *eth, u32 id, u32 config) + { +- int i; ++ u32 val; + + if (MTK_HAS_CAPS(eth->soc->caps, MTK_SOC_MT7628)) + return; + +- for (i = 0; i < MTK_MAX_DEVS; i++) { +- u32 val; +- +- if (!eth->netdev[i]) +- continue; ++ val = mtk_r32(eth, MTK_GDMA_FWD_CFG(id)); + +- val = mtk_r32(eth, MTK_GDMA_FWD_CFG(i)); ++ /* default setup the forward port to send frame to PDMA */ ++ val &= ~0xffff; + +- /* default setup the forward port to send frame to PDMA */ +- val &= ~0xffff; ++ /* Enable RX checksum */ ++ val |= MTK_GDMA_ICS_EN | MTK_GDMA_TCS_EN | MTK_GDMA_UCS_EN; + +- /* Enable RX checksum */ +- val |= MTK_GDMA_ICS_EN | MTK_GDMA_TCS_EN | MTK_GDMA_UCS_EN; ++ val |= config; + +- val |= config; ++ if (eth->netdev[id] && netdev_uses_dsa(eth->netdev[id])) ++ val |= MTK_GDMA_SPECIAL_TAG; + +- if (netdev_uses_dsa(eth->netdev[i])) +- val |= MTK_GDMA_SPECIAL_TAG; +- +- mtk_w32(eth, val, MTK_GDMA_FWD_CFG(i)); +- } +- /* Reset and enable PSE */ +- mtk_w32(eth, RST_GL_PSE, MTK_RST_GL); +- mtk_w32(eth, 0, MTK_RST_GL); ++ mtk_w32(eth, val, MTK_GDMA_FWD_CFG(id)); + } + + +@@ -3366,7 +3367,10 @@ static int mtk_open(struct net_device *d + { + struct mtk_mac *mac = netdev_priv(dev); + struct mtk_eth *eth = mac->hw; +- int i, err; ++ struct mtk_mac *target_mac; ++ int i, err, ppe_num; ++ ++ ppe_num = eth->soc->ppe_num; + + err = phylink_of_phy_connect(mac->phylink, mac->of_node, 0); + if (err) { +@@ -3390,18 +3394,38 @@ static int mtk_open(struct net_device *d + for (i = 0; i < ARRAY_SIZE(eth->ppe); i++) + mtk_ppe_start(eth->ppe[i]); + +- gdm_config = soc->offload_version ? soc->reg_map->gdma_to_ppe +- : MTK_GDMA_TO_PDMA; +- mtk_gdm_config(eth, gdm_config); ++ for (i = 0; i < MTK_MAX_DEVS; i++) { ++ if (!eth->netdev[i]) ++ break; ++ ++ target_mac = netdev_priv(eth->netdev[i]); ++ if (!soc->offload_version) { ++ target_mac->ppe_idx = 0; ++ gdm_config = MTK_GDMA_TO_PDMA; ++ } else if (ppe_num >= 3 && target_mac->id == 2) { ++ target_mac->ppe_idx = 2; ++ gdm_config = soc->reg_map->gdma_to_ppe[2]; ++ } else if (ppe_num >= 2 && target_mac->id == 1) { ++ target_mac->ppe_idx = 1; ++ gdm_config = soc->reg_map->gdma_to_ppe[1]; ++ } else { ++ target_mac->ppe_idx = 0; ++ gdm_config = soc->reg_map->gdma_to_ppe[0]; ++ } ++ mtk_gdm_config(eth, target_mac->id, gdm_config); ++ } ++ /* Reset and enable PSE */ ++ mtk_w32(eth, RST_GL_PSE, MTK_RST_GL); ++ mtk_w32(eth, 0, MTK_RST_GL); + + napi_enable(ð->tx_napi); + napi_enable(ð->rx_napi); + mtk_tx_irq_enable(eth, MTK_TX_DONE_INT); + mtk_rx_irq_enable(eth, soc->rx.irq_done_mask); + refcount_set(ð->dma_refcnt, 1); +- } +- else ++ } else { + refcount_inc(ð->dma_refcnt); ++ } + + phylink_start(mac->phylink); + netif_tx_start_all_queues(dev); +@@ -3478,7 +3502,8 @@ static int mtk_stop(struct net_device *d + if (!refcount_dec_and_test(ð->dma_refcnt)) + return 0; + +- mtk_gdm_config(eth, MTK_GDMA_DROP_ALL); ++ for (i = 0; i < MTK_MAX_DEVS; i++) ++ mtk_gdm_config(eth, i, MTK_GDMA_DROP_ALL); + + mtk_tx_irq_disable(eth, MTK_TX_DONE_INT); + mtk_rx_irq_disable(eth, eth->soc->rx.irq_done_mask); +@@ -4957,23 +4982,24 @@ static int mtk_probe(struct platform_dev + } + + if (eth->soc->offload_version) { +- u32 num_ppe = mtk_is_netsys_v2_or_greater(eth) ? 2 : 1; ++ u8 ppe_num = eth->soc->ppe_num; + +- num_ppe = min_t(u32, ARRAY_SIZE(eth->ppe), num_ppe); +- for (i = 0; i < num_ppe; i++) { +- u32 ppe_addr = eth->soc->reg_map->ppe_base + i * 0x400; ++ ppe_num = min_t(u8, ARRAY_SIZE(eth->ppe), ppe_num); ++ for (i = 0; i < ppe_num; i++) { ++ u32 ppe_addr = eth->soc->reg_map->ppe_base; + ++ ppe_addr += (i == 2 ? 0xc00 : i * 0x400); + eth->ppe[i] = mtk_ppe_init(eth, eth->base + ppe_addr, i); + + if (!eth->ppe[i]) { + err = -ENOMEM; + goto err_deinit_ppe; + } +- } ++ err = mtk_eth_offload_init(eth, i); + +- err = mtk_eth_offload_init(eth); +- if (err) +- goto err_deinit_ppe; ++ if (err) ++ goto err_deinit_ppe; ++ } + } + + for (i = 0; i < MTK_MAX_DEVS; i++) { +@@ -5076,6 +5102,7 @@ static const struct mtk_soc_data mt7621_ + .required_pctl = false, + .version = 1, + .offload_version = 1, ++ .ppe_num = 1, + .hash_offset = 2, + .foe_entry_size = MTK_FOE_ENTRY_V1_SIZE, + .tx = { +@@ -5104,6 +5131,7 @@ static const struct mtk_soc_data mt7622_ + .required_pctl = false, + .version = 1, + .offload_version = 2, ++ .ppe_num = 1, + .hash_offset = 2, + .has_accounting = true, + .foe_entry_size = MTK_FOE_ENTRY_V1_SIZE, +@@ -5132,6 +5160,7 @@ static const struct mtk_soc_data mt7623_ + .required_pctl = true, + .version = 1, + .offload_version = 1, ++ .ppe_num = 1, + .hash_offset = 2, + .foe_entry_size = MTK_FOE_ENTRY_V1_SIZE, + .disable_pll_modes = true, +@@ -5187,6 +5216,7 @@ static const struct mtk_soc_data mt7981_ + .required_pctl = false, + .version = 2, + .offload_version = 2, ++ .ppe_num = 2, + .hash_offset = 4, + .has_accounting = true, + .foe_entry_size = MTK_FOE_ENTRY_V2_SIZE, +@@ -5216,6 +5246,7 @@ static const struct mtk_soc_data mt7986_ + .required_pctl = false, + .version = 2, + .offload_version = 2, ++ .ppe_num = 2, + .hash_offset = 4, + .has_accounting = true, + .foe_entry_size = MTK_FOE_ENTRY_V2_SIZE, +@@ -5245,6 +5276,7 @@ static const struct mtk_soc_data mt7988_ + .required_pctl = false, + .version = 3, + .offload_version = 2, ++ .ppe_num = 3, + .hash_offset = 4, + .has_accounting = true, + .foe_entry_size = MTK_FOE_ENTRY_V3_SIZE, +--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h ++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h +@@ -1132,7 +1132,7 @@ struct mtk_reg_map { + u32 tx_sch_rate; /* tx scheduler rate control registers */ + } qdma; + u32 gdm1_cnt; +- u32 gdma_to_ppe; ++ u32 gdma_to_ppe[3]; + u32 ppe_base; + u32 wdma_base[3]; + u32 pse_iq_sta; +@@ -1170,6 +1170,7 @@ struct mtk_soc_data { + u8 offload_version; + u8 hash_offset; + u8 version; ++ u8 ppe_num; + u16 foe_entry_size; + netdev_features_t hw_features; + bool has_accounting; +@@ -1294,7 +1295,7 @@ struct mtk_eth { + + struct metadata_dst *dsa_meta[MTK_MAX_DSA_PORTS]; + +- struct mtk_ppe *ppe[2]; ++ struct mtk_ppe *ppe[3]; + struct rhashtable flow_table; + + struct bpf_prog __rcu *prog; +@@ -1319,6 +1320,7 @@ struct mtk_eth { + struct mtk_mac { + int id; + phy_interface_t interface; ++ u8 ppe_idx; + int speed; + struct device_node *of_node; + struct phylink *phylink; +@@ -1440,7 +1442,7 @@ int mtk_gmac_sgmii_path_setup(struct mtk + int mtk_gmac_gephy_path_setup(struct mtk_eth *eth, int mac_id); + int mtk_gmac_rgmii_path_setup(struct mtk_eth *eth, int mac_id); + +-int mtk_eth_offload_init(struct mtk_eth *eth); ++int mtk_eth_offload_init(struct mtk_eth *eth, u8 id); + int mtk_eth_setup_tc(struct net_device *dev, enum tc_setup_type type, + void *type_data); + int mtk_flow_offload_cmd(struct mtk_eth *eth, struct flow_cls_offload *cls, +--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c ++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c +@@ -245,10 +245,10 @@ mtk_flow_offload_replace(struct mtk_eth + int ppe_index) + { + struct flow_rule *rule = flow_cls_offload_flow_rule(f); ++ struct net_device *idev = NULL, *odev = NULL; + struct flow_action_entry *act; + struct mtk_flow_data data = {}; + struct mtk_foe_entry foe; +- struct net_device *odev = NULL; + struct mtk_flow_entry *entry; + int offload_type = 0; + int wed_index = -1; +@@ -264,6 +264,17 @@ mtk_flow_offload_replace(struct mtk_eth + struct flow_match_meta match; + + flow_rule_match_meta(rule, &match); ++ if (mtk_is_netsys_v2_or_greater(eth)) { ++ idev = __dev_get_by_index(&init_net, match.key->ingress_ifindex); ++ if (idev) { ++ struct mtk_mac *mac = netdev_priv(idev); ++ ++ if (WARN_ON(mac->ppe_idx >= eth->soc->ppe_num)) ++ return -EINVAL; ++ ++ ppe_index = mac->ppe_idx; ++ } ++ } + } else { + return -EOPNOTSUPP; + } +@@ -630,7 +641,9 @@ int mtk_eth_setup_tc(struct net_device * + } + } + +-int mtk_eth_offload_init(struct mtk_eth *eth) ++int mtk_eth_offload_init(struct mtk_eth *eth, u8 id) + { ++ if (!eth->ppe[id] || !eth->ppe[id]->foe_table) ++ return 0; + return rhashtable_init(ð->flow_table, &mtk_flow_ht_params); + } diff --git a/lede/target/linux/generic/backport-6.6/752-27-v6.10-net-ethernet-mtk_eth_soc-ppe-prevent-ppe-update-for-.patch b/lede/target/linux/generic/backport-6.6/752-27-v6.10-net-ethernet-mtk_eth_soc-ppe-prevent-ppe-update-for-.patch new file mode 100644 index 0000000000..dbf574d80a --- /dev/null +++ b/lede/target/linux/generic/backport-6.6/752-27-v6.10-net-ethernet-mtk_eth_soc-ppe-prevent-ppe-update-for-.patch @@ -0,0 +1,30 @@ +From 73cfd947dbdb25ef9863ac49c4596a7d53ad4025 Mon Sep 17 00:00:00 2001 +From: Elad Yifee +Date: Sun, 23 Jun 2024 20:51:09 +0300 +Subject: [PATCH] net: ethernet: mtk_eth_soc: ppe: prevent ppe update for + non-mtk devices + +Introduce an additional validation to ensure that the PPE index +is modified exclusively for mtk_eth ingress devices. +This primarily addresses the issue related +to WED operation with multiple PPEs. + +Fixes: dee4dd10c79a ("net: ethernet: mtk_eth_soc: ppe: add support for multiple PPEs") +Signed-off-by: Elad Yifee +Link: https://lore.kernel.org/r/20240623175113.24437-1-eladwf@gmail.com +Signed-off-by: Paolo Abeni +--- + drivers/net/ethernet/mediatek/mtk_ppe_offload.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c ++++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c +@@ -266,7 +266,7 @@ mtk_flow_offload_replace(struct mtk_eth + flow_rule_match_meta(rule, &match); + if (mtk_is_netsys_v2_or_greater(eth)) { + idev = __dev_get_by_index(&init_net, match.key->ingress_ifindex); +- if (idev) { ++ if (idev && idev->netdev_ops == eth->netdev[0]->netdev_ops) { + struct mtk_mac *mac = netdev_priv(idev); + + if (WARN_ON(mac->ppe_idx >= eth->soc->ppe_num)) diff --git a/lede/target/linux/generic/pending-6.6/702-net-ethernet-mtk_eth_soc-enable-threaded-NAPI.patch b/lede/target/linux/generic/pending-6.6/702-net-ethernet-mtk_eth_soc-enable-threaded-NAPI.patch index f528e35d75..4fbf6288c8 100644 --- a/lede/target/linux/generic/pending-6.6/702-net-ethernet-mtk_eth_soc-enable-threaded-NAPI.patch +++ b/lede/target/linux/generic/pending-6.6/702-net-ethernet-mtk_eth_soc-enable-threaded-NAPI.patch @@ -10,7 +10,7 @@ Signed-off-by: Felix Fietkau --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c -@@ -4994,6 +4994,8 @@ static int mtk_probe(struct platform_dev +@@ -5020,6 +5020,8 @@ static int mtk_probe(struct platform_dev * for NAPI to work */ init_dummy_netdev(ð->dummy_dev); diff --git a/lede/target/linux/generic/pending-6.6/732-00-net-ethernet-mtk_eth_soc-compile-out-netsys-v2-code-.patch b/lede/target/linux/generic/pending-6.6/732-00-net-ethernet-mtk_eth_soc-compile-out-netsys-v2-code-.patch index cf19523160..c3297a1087 100644 --- a/lede/target/linux/generic/pending-6.6/732-00-net-ethernet-mtk_eth_soc-compile-out-netsys-v2-code-.patch +++ b/lede/target/linux/generic/pending-6.6/732-00-net-ethernet-mtk_eth_soc-compile-out-netsys-v2-code-.patch @@ -11,7 +11,7 @@ Signed-off-by: Felix Fietkau --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h -@@ -1334,6 +1334,22 @@ struct mtk_mac { +@@ -1336,6 +1336,22 @@ struct mtk_mac { /* the struct describing the SoC. these are declared in the soc_xyz.c files */ extern const struct of_device_id of_mtk_match[]; @@ -34,7 +34,7 @@ Signed-off-by: Felix Fietkau static inline bool mtk_is_netsys_v1(struct mtk_eth *eth) { return eth->soc->version == 1; -@@ -1348,6 +1364,7 @@ static inline bool mtk_is_netsys_v3_or_g +@@ -1350,6 +1366,7 @@ static inline bool mtk_is_netsys_v3_or_g { return eth->soc->version > 2; } diff --git a/lede/target/linux/generic/pending-6.6/732-01-net-ethernet-mtk_eth_soc-work-around-issue-with-send.patch b/lede/target/linux/generic/pending-6.6/732-01-net-ethernet-mtk_eth_soc-work-around-issue-with-send.patch index 3e56661e70..53187934d0 100644 --- a/lede/target/linux/generic/pending-6.6/732-01-net-ethernet-mtk_eth_soc-work-around-issue-with-send.patch +++ b/lede/target/linux/generic/pending-6.6/732-01-net-ethernet-mtk_eth_soc-work-around-issue-with-send.patch @@ -24,7 +24,7 @@ Signed-off-by: Felix Fietkau #include #include "mtk_eth_soc.h" -@@ -1587,12 +1588,28 @@ static void mtk_wake_queue(struct mtk_et +@@ -1596,12 +1597,28 @@ static void mtk_wake_queue(struct mtk_et } } @@ -53,7 +53,7 @@ Signed-off-by: Felix Fietkau bool gso = false; int tx_num; -@@ -1614,6 +1631,18 @@ static netdev_tx_t mtk_start_xmit(struct +@@ -1623,6 +1640,18 @@ static netdev_tx_t mtk_start_xmit(struct return NETDEV_TX_BUSY; } @@ -72,7 +72,7 @@ Signed-off-by: Felix Fietkau /* TSO: fill MSS info in tcp checksum field */ if (skb_is_gso(skb)) { if (skb_cow_head(skb, 0)) { -@@ -1629,8 +1658,14 @@ static netdev_tx_t mtk_start_xmit(struct +@@ -1638,8 +1667,14 @@ static netdev_tx_t mtk_start_xmit(struct } } diff --git a/lede/target/linux/generic/pending-6.6/733-01-net-ethernet-mtk_eth_soc-use-napi_build_skb.patch b/lede/target/linux/generic/pending-6.6/733-01-net-ethernet-mtk_eth_soc-use-napi_build_skb.patch index f81126430f..82ba768fd5 100644 --- a/lede/target/linux/generic/pending-6.6/733-01-net-ethernet-mtk_eth_soc-use-napi_build_skb.patch +++ b/lede/target/linux/generic/pending-6.6/733-01-net-ethernet-mtk_eth_soc-use-napi_build_skb.patch @@ -10,7 +10,7 @@ Signed-off-by: Felix Fietkau --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c -@@ -2129,7 +2129,7 @@ static int mtk_poll_rx(struct napi_struc +@@ -2140,7 +2140,7 @@ static int mtk_poll_rx(struct napi_struc if (ret != XDP_PASS) goto skip_rx; @@ -19,7 +19,7 @@ Signed-off-by: Felix Fietkau if (unlikely(!skb)) { page_pool_put_full_page(ring->page_pool, page, true); -@@ -2167,7 +2167,7 @@ static int mtk_poll_rx(struct napi_struc +@@ -2178,7 +2178,7 @@ static int mtk_poll_rx(struct napi_struc dma_unmap_single(eth->dma_dev, ((u64)trxd.rxd1 | addr64), ring->buf_size, DMA_FROM_DEVICE); diff --git a/lede/target/linux/generic/pending-6.6/737-net-ethernet-mtk_eth_soc-add-paths-and-SerDes-modes-.patch b/lede/target/linux/generic/pending-6.6/737-net-ethernet-mtk_eth_soc-add-paths-and-SerDes-modes-.patch index 3a79e1a553..89dc87e1a2 100644 --- a/lede/target/linux/generic/pending-6.6/737-net-ethernet-mtk_eth_soc-add-paths-and-SerDes-modes-.patch +++ b/lede/target/linux/generic/pending-6.6/737-net-ethernet-mtk_eth_soc-add-paths-and-SerDes-modes-.patch @@ -214,7 +214,7 @@ Signed-off-by: Daniel Golle #include #include #include -@@ -261,12 +263,8 @@ static const char * const mtk_clks_sourc +@@ -270,12 +272,8 @@ static const char * const mtk_clks_sourc "ethwarp_wocpu2", "ethwarp_wocpu1", "ethwarp_wocpu0", @@ -227,7 +227,7 @@ Signed-off-by: Daniel Golle "top_eth_gmii_sel", "top_eth_refck_50m_sel", "top_eth_sys_200m_sel", -@@ -509,6 +507,30 @@ static void mtk_setup_bridge_switch(stru +@@ -518,6 +516,30 @@ static void mtk_setup_bridge_switch(stru MTK_GSW_CFG); } @@ -258,7 +258,7 @@ Signed-off-by: Daniel Golle static struct phylink_pcs *mtk_mac_select_pcs(struct phylink_config *config, phy_interface_t interface) { -@@ -517,6 +539,21 @@ static struct phylink_pcs *mtk_mac_selec +@@ -526,6 +548,21 @@ static struct phylink_pcs *mtk_mac_selec struct mtk_eth *eth = mac->hw; unsigned int sid; @@ -280,7 +280,7 @@ Signed-off-by: Daniel Golle if (interface == PHY_INTERFACE_MODE_SGMII || phy_interface_mode_is_8023z(interface)) { sid = (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_SGMII)) ? -@@ -568,7 +605,22 @@ static void mtk_mac_config(struct phylin +@@ -577,7 +614,22 @@ static void mtk_mac_config(struct phylin goto init_err; } break; @@ -303,7 +303,7 @@ Signed-off-by: Daniel Golle break; default: goto err_phy; -@@ -615,8 +667,6 @@ static void mtk_mac_config(struct phylin +@@ -624,8 +676,6 @@ static void mtk_mac_config(struct phylin val &= ~SYSCFG0_GE_MODE(SYSCFG0_GE_MASK, mac->id); val |= SYSCFG0_GE_MODE(ge_mode, mac->id); regmap_write(eth->ethsys, ETHSYS_SYSCFG0, val); @@ -312,7 +312,7 @@ Signed-off-by: Daniel Golle } /* SGMII */ -@@ -633,21 +683,40 @@ static void mtk_mac_config(struct phylin +@@ -642,21 +692,40 @@ static void mtk_mac_config(struct phylin /* Save the syscfg0 value for mac_finish */ mac->syscfg0 = val; @@ -360,7 +360,7 @@ Signed-off-by: Daniel Golle return; err_phy: -@@ -660,6 +729,18 @@ init_err: +@@ -669,6 +738,18 @@ init_err: mac->id, phy_modes(state->interface), err); } @@ -379,7 +379,7 @@ Signed-off-by: Daniel Golle static int mtk_mac_finish(struct phylink_config *config, unsigned int mode, phy_interface_t interface) { -@@ -668,6 +749,10 @@ static int mtk_mac_finish(struct phylink +@@ -677,6 +758,10 @@ static int mtk_mac_finish(struct phylink struct mtk_eth *eth = mac->hw; u32 mcr_cur, mcr_new; @@ -390,7 +390,7 @@ Signed-off-by: Daniel Golle /* Enable SGMII */ if (interface == PHY_INTERFACE_MODE_SGMII || phy_interface_mode_is_8023z(interface)) -@@ -692,10 +777,14 @@ static void mtk_mac_link_down(struct phy +@@ -701,10 +786,14 @@ static void mtk_mac_link_down(struct phy { struct mtk_mac *mac = container_of(config, struct mtk_mac, phylink_config); @@ -408,7 +408,7 @@ Signed-off-by: Daniel Golle } static void mtk_set_queue_speed(struct mtk_eth *eth, unsigned int idx, -@@ -767,13 +856,11 @@ static void mtk_set_queue_speed(struct m +@@ -776,13 +865,11 @@ static void mtk_set_queue_speed(struct m mtk_w32(eth, val, soc->reg_map->qdma.qtx_sch + ofs); } @@ -426,7 +426,7 @@ Signed-off-by: Daniel Golle u32 mcr; mcr = mtk_r32(mac->hw, MTK_MAC_MCR(mac->id)); -@@ -807,9 +894,63 @@ static void mtk_mac_link_up(struct phyli +@@ -816,9 +903,63 @@ static void mtk_mac_link_up(struct phyli mtk_w32(mac->hw, mcr, MTK_MAC_MCR(mac->id)); } @@ -490,9 +490,9 @@ Signed-off-by: Daniel Golle .mac_finish = mtk_mac_finish, .mac_link_down = mtk_mac_link_down, .mac_link_up = mtk_mac_link_up, -@@ -3403,6 +3544,9 @@ static int mtk_open(struct net_device *d - struct mtk_eth *eth = mac->hw; - int i, err; +@@ -3407,6 +3548,9 @@ static int mtk_open(struct net_device *d + + ppe_num = eth->soc->ppe_num; + if (mac->pextp) + phy_power_on(mac->pextp); @@ -500,7 +500,7 @@ Signed-off-by: Daniel Golle err = phylink_of_phy_connect(mac->phylink, mac->of_node, 0); if (err) { netdev_err(dev, "%s: could not attach PHY: %d\n", __func__, -@@ -3532,6 +3676,9 @@ static int mtk_stop(struct net_device *d +@@ -3557,6 +3701,9 @@ static int mtk_stop(struct net_device *d for (i = 0; i < ARRAY_SIZE(eth->ppe); i++) mtk_ppe_stop(eth->ppe[i]); @@ -510,7 +510,7 @@ Signed-off-by: Daniel Golle return 0; } -@@ -4529,6 +4676,7 @@ static const struct net_device_ops mtk_n +@@ -4554,6 +4701,7 @@ static const struct net_device_ops mtk_n static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np) { const __be32 *_id = of_get_property(np, "reg", NULL); @@ -518,7 +518,7 @@ Signed-off-by: Daniel Golle phy_interface_t phy_mode; struct phylink *phylink; struct mtk_mac *mac; -@@ -4565,16 +4713,41 @@ static int mtk_add_mac(struct mtk_eth *e +@@ -4590,16 +4738,41 @@ static int mtk_add_mac(struct mtk_eth *e mac->id = id; mac->hw = eth; mac->of_node = np; @@ -568,7 +568,7 @@ Signed-off-by: Daniel Golle } memset(mac->hwlro_ip, 0, sizeof(mac->hwlro_ip)); -@@ -4657,8 +4830,21 @@ static int mtk_add_mac(struct mtk_eth *e +@@ -4682,8 +4855,21 @@ static int mtk_add_mac(struct mtk_eth *e phy_interface_zero(mac->phylink_config.supported_interfaces); __set_bit(PHY_INTERFACE_MODE_INTERNAL, mac->phylink_config.supported_interfaces); @@ -590,7 +590,7 @@ Signed-off-by: Daniel Golle phylink = phylink_create(&mac->phylink_config, of_fwnode_handle(mac->of_node), phy_mode, &mtk_phylink_ops); -@@ -4709,6 +4895,26 @@ free_netdev: +@@ -4734,6 +4920,26 @@ free_netdev: return err; } @@ -617,7 +617,7 @@ Signed-off-by: Daniel Golle void mtk_eth_set_dma_device(struct mtk_eth *eth, struct device *dma_dev) { struct net_device *dev, *tmp; -@@ -4855,7 +5061,8 @@ static int mtk_probe(struct platform_dev +@@ -4880,7 +5086,8 @@ static int mtk_probe(struct platform_dev regmap_write(cci, 0, 3); } @@ -627,7 +627,7 @@ Signed-off-by: Daniel Golle err = mtk_sgmii_init(eth); if (err) -@@ -4966,6 +5173,24 @@ static int mtk_probe(struct platform_dev +@@ -4991,6 +5198,24 @@ static int mtk_probe(struct platform_dev } } @@ -652,7 +652,7 @@ Signed-off-by: Daniel Golle if (MTK_HAS_CAPS(eth->soc->caps, MTK_SHARED_INT)) { err = devm_request_irq(eth->dev, eth->irq[0], mtk_handle_irq, 0, -@@ -5068,6 +5293,11 @@ static int mtk_remove(struct platform_de +@@ -5094,6 +5319,11 @@ static int mtk_remove(struct platform_de mtk_stop(eth->netdev[i]); mac = netdev_priv(eth->netdev[i]); phylink_disconnect_phy(mac->phylink); @@ -893,7 +893,7 @@ Signed-off-by: Daniel Golle struct mtk_tx_dma_desc_info { dma_addr_t addr; -@@ -1322,6 +1379,9 @@ struct mtk_mac { +@@ -1324,6 +1381,9 @@ struct mtk_mac { struct device_node *of_node; struct phylink *phylink; struct phylink_config phylink_config; @@ -903,7 +903,7 @@ Signed-off-by: Daniel Golle struct mtk_eth *hw; struct mtk_hw_stats *hw_stats; __be32 hwlro_ip[MTK_MAX_LRO_IP_CNT]; -@@ -1445,6 +1505,19 @@ static inline u32 mtk_get_ib2_multicast_ +@@ -1447,6 +1507,19 @@ static inline u32 mtk_get_ib2_multicast_ return MTK_FOE_IB2_MULTICAST; } @@ -923,7 +923,7 @@ Signed-off-by: Daniel Golle /* read the hardware status register */ void mtk_stats_update_mac(struct mtk_mac *mac); -@@ -1453,8 +1526,10 @@ u32 mtk_r32(struct mtk_eth *eth, unsigne +@@ -1455,8 +1528,10 @@ u32 mtk_r32(struct mtk_eth *eth, unsigne u32 mtk_m32(struct mtk_eth *eth, u32 mask, u32 set, unsigned int reg); int mtk_gmac_sgmii_path_setup(struct mtk_eth *eth, int mac_id); @@ -932,5 +932,5 @@ Signed-off-by: Daniel Golle int mtk_gmac_rgmii_path_setup(struct mtk_eth *eth, int mac_id); +int mtk_gmac_usxgmii_path_setup(struct mtk_eth *eth, int mac_id); - int mtk_eth_offload_init(struct mtk_eth *eth); + int mtk_eth_offload_init(struct mtk_eth *eth, u8 id); int mtk_eth_setup_tc(struct net_device *dev, enum tc_setup_type type, diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/iptables.sh b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/iptables.sh index 07dcc30796..b718166b30 100755 --- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/iptables.sh +++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/iptables.sh @@ -225,6 +225,8 @@ load_acl() { use_block_list=${USE_BLOCK_LIST} use_gfw_list=${USE_GFW_LIST} chn_list=${CHN_LIST} + tcp_proxy_mode=${TCP_PROXY_MODE} + udp_proxy_mode=${UDP_PROXY_MODE} } for i in $(cat ${TMP_ACL_PATH}/${sid}/rule_list); do diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/nftables.sh b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/nftables.sh index f8f3401422..ca75f6904b 100755 --- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/nftables.sh +++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/nftables.sh @@ -258,6 +258,8 @@ load_acl() { use_block_list=${USE_BLOCK_LIST} use_gfw_list=${USE_GFW_LIST} chn_list=${CHN_LIST} + tcp_proxy_mode=${TCP_PROXY_MODE} + udp_proxy_mode=${UDP_PROXY_MODE} } for i in $(cat ${TMP_ACL_PATH}/${sid}/rule_list); do diff --git a/small/luci-app-passwall/root/usr/share/passwall/iptables.sh b/small/luci-app-passwall/root/usr/share/passwall/iptables.sh index 07dcc30796..b718166b30 100755 --- a/small/luci-app-passwall/root/usr/share/passwall/iptables.sh +++ b/small/luci-app-passwall/root/usr/share/passwall/iptables.sh @@ -225,6 +225,8 @@ load_acl() { use_block_list=${USE_BLOCK_LIST} use_gfw_list=${USE_GFW_LIST} chn_list=${CHN_LIST} + tcp_proxy_mode=${TCP_PROXY_MODE} + udp_proxy_mode=${UDP_PROXY_MODE} } for i in $(cat ${TMP_ACL_PATH}/${sid}/rule_list); do diff --git a/small/luci-app-passwall/root/usr/share/passwall/nftables.sh b/small/luci-app-passwall/root/usr/share/passwall/nftables.sh index f8f3401422..ca75f6904b 100755 --- a/small/luci-app-passwall/root/usr/share/passwall/nftables.sh +++ b/small/luci-app-passwall/root/usr/share/passwall/nftables.sh @@ -258,6 +258,8 @@ load_acl() { use_block_list=${USE_BLOCK_LIST} use_gfw_list=${USE_GFW_LIST} chn_list=${CHN_LIST} + tcp_proxy_mode=${TCP_PROXY_MODE} + udp_proxy_mode=${UDP_PROXY_MODE} } for i in $(cat ${TMP_ACL_PATH}/${sid}/rule_list); do diff --git a/v2rayng/V2rayNG/app/build.gradle.kts b/v2rayng/V2rayNG/app/build.gradle.kts index ac09ff4e6a..1ea9057bb2 100644 --- a/v2rayng/V2rayNG/app/build.gradle.kts +++ b/v2rayng/V2rayNG/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "com.v2ray.ang" minSdk = 21 targetSdk = 34 - versionCode = 565 - versionName = "1.8.26" + versionCode = 570 + versionName = "1.8.27" multiDexEnabled = true splits.abi { reset() @@ -53,14 +53,14 @@ android { splits { abi { isEnable = true - isUniversalApk = false + isUniversalApk = true } } applicationVariants.all { val variant = this val versionCodes = - mapOf("armeabi-v7a" to 1, "arm64-v8a" to 2, "x86" to 3, "x86_64" to 4) + mapOf("armeabi-v7a" to 4, "arm64-v8a" to 4, "x86" to 4, "x86_64" to 4, "all" to 4) variant.outputs .map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl } @@ -86,6 +86,12 @@ android { viewBinding = true buildConfig = true } + + packagingOptions { + jniLibs { + useLegacyPackaging = true + } + } } dependencies { @@ -100,15 +106,15 @@ dependencies { implementation("androidx.cardview:cardview:1.0.0") implementation("androidx.preference:preference-ktx:1.2.1") implementation("androidx.recyclerview:recyclerview:1.3.2") - implementation("androidx.fragment:fragment-ktx:1.7.1") + implementation("androidx.fragment:fragment-ktx:1.8.1") implementation("androidx.multidex:multidex:2.0.1") implementation("androidx.viewpager2:viewpager2:1.1.0") // Androidx ktx implementation("androidx.activity:activity-ktx:1.9.0") - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.1") - implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.1") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.1") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.2") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.2") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.2") //kotlin implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.23") diff --git a/v2rayng/V2rayNG/app/src/main/AndroidManifest.xml b/v2rayng/V2rayNG/app/src/main/AndroidManifest.xml index d5f544193e..42cfc26180 100644 --- a/v2rayng/V2rayNG/app/src/main/AndroidManifest.xml +++ b/v2rayng/V2rayNG/app/src/main/AndroidManifest.xml @@ -54,7 +54,7 @@ - + diff --git a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index 636e0e98a4..464ab6e607 100644 --- a/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/v2rayng/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -2,9 +2,11 @@ package com.v2ray.ang.util import android.content.Context import android.text.TextUtils +import android.util.Log import com.google.gson.Gson import com.tencent.mmkv.MMKV import com.v2ray.ang.AppConfig +import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM import com.v2ray.ang.AppConfig.TAG_DIRECT import com.v2ray.ang.AppConfig.TAG_FRAGMENT @@ -49,6 +51,12 @@ object V2rayConfigUtil { return Result(true, customConfig) } val outbound = config.getProxyOutbound() ?: return Result(false, "") + val address = outbound.getServerAddress() ?: return Result(false, "") + if (!Utils.isIpAddress(address) && !Utils.isValidUrl(address)) { + Log.d(ANG_PACKAGE, "$address is an invalid ip or domain") + return Result(false, "") + } + val result = getV2rayNonCustomConfig(context, outbound, config.remarks) //Log.d(ANG_PACKAGE, result.content) return result diff --git a/v2rayu/V2rayU.xcodeproj/project.pbxproj b/v2rayu/V2rayU.xcodeproj/project.pbxproj index 912cac3482..e8cbf8a33f 100755 --- a/v2rayu/V2rayU.xcodeproj/project.pbxproj +++ b/v2rayu/V2rayU.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 66193A8923EE46BC00289B6A /* PreferenceRouting.xib in Resources */ = {isa = PBXBuildFile; fileRef = 66193A8723EE46BC00289B6A /* PreferenceRouting.xib */; }; 6633A43F2C0A120000C54CA5 /* Sparkle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6633A43E2C0A120000C54CA5 /* Sparkle.swift */; }; 663F040625ED4B2C00687600 /* V2rayLaunch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663F040525ED4B2C00687600 /* V2rayLaunch.swift */; }; + 664BAC472C2DB0E100654FB7 /* V2rayRouting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664BAC462C2DB0E100654FB7 /* V2rayRouting.swift */; }; 664EB375216C9A5E00B6AE0D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664EB374216C9A5E00B6AE0D /* AppDelegate.swift */; }; 664EB377216C9A5F00B6AE0D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 664EB376216C9A5F00B6AE0D /* Assets.xcassets */; }; 664EB392216CA9E800B6AE0D /* ConfigWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664EB390216CA9E800B6AE0D /* ConfigWindow.swift */; }; @@ -124,6 +125,7 @@ 663F040525ED4B2C00687600 /* V2rayLaunch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = V2rayLaunch.swift; sourceTree = ""; }; 664666A021CBD6C60094F0B7 /* libPods-V2rayUTool.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libPods-V2rayUTool.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 664B95DD217062A500DBC941 /* Alamofire */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Alamofire; path = Pods/Alamofire; sourceTree = ""; }; + 664BAC462C2DB0E100654FB7 /* V2rayRouting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V2rayRouting.swift; sourceTree = ""; }; 664EB371216C9A5E00B6AE0D /* V2rayU.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = V2rayU.app; sourceTree = BUILT_PRODUCTS_DIR; }; 664EB374216C9A5E00B6AE0D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 664EB376216C9A5F00B6AE0D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -272,6 +274,7 @@ 660D0E59216E0158000C2922 /* V2rayServer.swift */, 663F040525ED4B2C00687600 /* V2rayLaunch.swift */, 66BC2B88228C589E00FBB716 /* V2raySubscription.swift */, + 664BAC462C2DB0E100654FB7 /* V2rayRouting.swift */, 66784AFB2170486D00AD307F /* Util.swift */, 6D6DF87840CDA04AF0E9D36E /* Import.swift */, 664EB37B216C9A5F00B6AE0D /* Info.plist */, @@ -544,6 +547,7 @@ 66AD5335241497000070529C /* Shortcut.m in Sources */, 663F040625ED4B2C00687600 /* V2rayLaunch.swift in Sources */, 6608D9E22182C9C100A0E0DD /* v2rayStream.swift in Sources */, + 664BAC472C2DB0E100654FB7 /* V2rayRouting.swift in Sources */, 66784AFC2170486D00AD307F /* Util.swift in Sources */, 660D0E5A216E0158000C2922 /* V2rayServer.swift in Sources */, 6608D9DC2182C6D200A0E0DD /* v2rayInbound.swift in Sources */, diff --git a/v2rayu/V2rayU/AppDelegate.swift b/v2rayu/V2rayU/AppDelegate.swift index ceb1e64947..48a0c377e6 100644 --- a/v2rayu/V2rayU/AppDelegate.swift +++ b/v2rayu/V2rayU/AppDelegate.swift @@ -35,8 +35,8 @@ let preferencesWindowController = PreferencesWindowController( PreferenceGeneralViewController(), PreferenceAdvanceViewController(), PreferenceSubscribeViewController(), - PreferencePacViewController(), PreferenceRoutingViewController(), + PreferencePacViewController(), PreferenceDnsViewController(), PreferenceAboutViewController(), ] @@ -112,6 +112,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { UserDefaults.set(forKey: .runMode, value: RunMode.global.rawValue) } V2rayServer.loadConfig() + V2rayRoutings.loadConfig() + V2raySubscription.loadConfig() } @objc func handleAppleEvent(event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) { diff --git a/v2rayu/V2rayU/Base.lproj/MainMenu.xib b/v2rayu/V2rayU/Base.lproj/MainMenu.xib index b3af050f71..6be17c81fb 100644 --- a/v2rayu/V2rayU/Base.lproj/MainMenu.xib +++ b/v2rayu/V2rayU/Base.lproj/MainMenu.xib @@ -24,7 +24,7 @@ - + @@ -88,38 +88,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/v2rayu/V2rayU/Base.lproj/PreferencePac.xib b/v2rayu/V2rayU/Base.lproj/PreferencePac.xib index d2a11caa2b..3d507a4334 100755 --- a/v2rayu/V2rayU/Base.lproj/PreferencePac.xib +++ b/v2rayu/V2rayU/Base.lproj/PreferencePac.xib @@ -1,8 +1,8 @@ - + - + @@ -23,7 +23,7 @@ - + @@ -32,7 +32,7 @@ - + @@ -41,7 +41,7 @@ - + @@ -94,7 +94,7 @@ - + diff --git a/v2rayu/V2rayU/Base.lproj/PreferenceRouting.xib b/v2rayu/V2rayU/Base.lproj/PreferenceRouting.xib index 9eb6963705..ac68e19e77 100755 --- a/v2rayu/V2rayU/Base.lproj/PreferenceRouting.xib +++ b/v2rayu/V2rayU/Base.lproj/PreferenceRouting.xib @@ -8,11 +8,18 @@ - - - - - + + + + + + + + + + + + @@ -23,187 +30,332 @@ - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + @@ -245,4 +384,8 @@ + + + + diff --git a/v2rayu/V2rayU/Base.lproj/PreferenceSubscription.xib b/v2rayu/V2rayU/Base.lproj/PreferenceSubscription.xib index e9e32795d7..5ad25bb4ba 100644 --- a/v2rayu/V2rayU/Base.lproj/PreferenceSubscription.xib +++ b/v2rayu/V2rayU/Base.lproj/PreferenceSubscription.xib @@ -1,8 +1,8 @@ - + - + @@ -28,7 +28,7 @@ - + @@ -107,11 +107,11 @@ - + - - + + @@ -139,11 +139,11 @@ - + - - + + @@ -179,7 +179,7 @@ - + @@ -188,7 +188,7 @@ - + @@ -197,7 +197,7 @@ - + @@ -206,7 +206,7 @@ - + diff --git a/v2rayu/V2rayU/ConfigWindow.swift b/v2rayu/V2rayU/ConfigWindow.swift index a6e91eee24..5d5c7b9c50 100644 --- a/v2rayu/V2rayU/ConfigWindow.swift +++ b/v2rayu/V2rayU/ConfigWindow.swift @@ -188,23 +188,23 @@ class ConfigWindowController: NSWindowController, NSWindowDelegate, NSTabViewDel // delete server config case 1: - // get seleted index - let idx = self.serversTableView.selectedRow - // remove - V2rayServer.remove(idx: idx) - - // reload - V2rayServer.loadConfig() - menuController.showServers() - - // selected prev row - let cnt: Int = V2rayServer.count() - var rowIndex: Int = idx - 1 - if idx > 0 && idx < cnt { - rowIndex = idx - } - DispatchQueue.main.sync { + // get seleted index + let idx = self.serversTableView.selectedRow + // remove + V2rayServer.remove(idx: idx) + + // reload + V2rayServer.loadConfig() + menuController.showServers() + + // selected prev row + let cnt: Int = V2rayServer.count() + var rowIndex: Int = idx - 1 + if idx > 0 && idx < cnt { + rowIndex = idx + } + // reload self.serversTableView.reloadData() // fix diff --git a/v2rayu/V2rayU/MainMenu.swift b/v2rayu/V2rayU/MainMenu.swift index cc9a6a6918..5152a70b1b 100644 --- a/v2rayu/V2rayU/MainMenu.swift +++ b/v2rayu/V2rayU/MainMenu.swift @@ -27,7 +27,7 @@ class MenuController: NSObject, NSMenuDelegate { @IBOutlet weak var v2rayStatusItem: NSMenuItem! @IBOutlet weak var serverItems: NSMenuItem! @IBOutlet weak var newVersionItem: NSMenuItem! - @IBOutlet weak var routingMenu: NSMenu! + @IBOutlet weak var routingMenu: NSMenuItem! // when menu.xib loaded override func awakeFromNib() { @@ -39,7 +39,7 @@ class MenuController: NSObject, NSMenuDelegate { // hide new version newVersionItem.isHidden = true - self.showRouring(); + self.showRouting(); // windowWillClose Notification NotificationCenter.default.addObserver(self, selector: #selector(configWindowWillClose(notification:)), name: NSWindow.willCloseNotification, object: nil) @@ -127,21 +127,40 @@ class MenuController: NSObject, NSMenuDelegate { } } - func showRouring() { - let routingRule = Int(UserDefaults.get(forKey: .routingRule) ?? "0") ?? 0 - DispatchQueue.main.async { - // 假设 routingMenu 已经连接并且有一个子菜单 - if self.routingMenu.items != nil { - // 确保 routingMenu 已经连接 - for item in self.routingMenu.items { - if item.tag == routingRule { - item.state = .on - } else { - item.state = .off - } - } - } - } + func showRouting() { + DispatchQueue.global().async { + let rules = V2rayRoutings.all() + print("showRouting",rules) + let sumMenus = NSMenu() + // add Routing... menu and click event is goRouting + let routingMenuItem = NSMenuItem() + routingMenuItem.title = "Routing..." + routingMenuItem.target = self + routingMenuItem.action = #selector(self.goRouting(_:)) + sumMenus.addItem(routingMenuItem) + // add separator menu item + let separator = NSMenuItem.separator() + sumMenus.addItem(separator) + // now add all rules + let routingRule = UserDefaults.get(forKey: .routingSelectedRule) + for rule in rules { + let menuItem = NSMenuItem() + menuItem.title = rule.remark + menuItem.target = self + menuItem.action = #selector(self.switchRouting(_:)) + menuItem.representedObject = rule + menuItem.isEnabled = true + if rule.name == routingRule { + menuItem.state = NSControl.StateValue.on + } + sumMenus.addItem(menuItem) + } + print("showRouting",sumMenus) + // 假设 routingMenu 已经连接并且有一个子菜单 + DispatchQueue.main.async { + self.routingMenu.submenu = sumMenus + } + } } func getServerMenus() -> NSMenu { @@ -261,6 +280,16 @@ class MenuController: NSObject, NSMenuDelegate { V2rayLaunch.restartV2ray() } + @IBAction func switchRouting(_ sender: NSMenuItem) { + guard let obj = sender.representedObject as? RoutingItem else { + NSLog("switchRouting err") + return + } + UserDefaults.set(forKey: .routingSelectedRule, value: obj.name); + self.showRouting(); + V2rayLaunch.restartV2ray() + } + @IBAction func openConfig(_ sender: NSMenuItem) { OpenConfigWindow() } @@ -300,12 +329,6 @@ class MenuController: NSObject, NSMenuDelegate { } } - @IBAction func switchRouting(_ sender: NSMenuItem) { - UserDefaults.set(forKey: .routingRule, value: String(sender.tag)); - self.showRouring(); - V2rayLaunch.restartV2ray() - } - @IBAction func switchGlobalMode(_ sender: NSMenuItem) { UserDefaults.set(forKey: .runMode, value: RunMode.global.rawValue) V2rayLaunch.restartV2ray() diff --git a/v2rayu/V2rayU/Preference/PreferenceRouting.swift b/v2rayu/V2rayU/Preference/PreferenceRouting.swift index dcaf8db135..b4e0cb77be 100644 --- a/v2rayu/V2rayU/Preference/PreferenceRouting.swift +++ b/v2rayu/V2rayU/Preference/PreferenceRouting.swift @@ -9,18 +9,26 @@ import Cocoa import Preferences -final class PreferenceRoutingViewController: NSViewController, PreferencePane { +final class PreferenceRoutingViewController: NSViewController, PreferencePane, NSTabViewDelegate { let preferencePaneIdentifier = PreferencePane.Identifier.routingTab let preferencePaneTitle = "Routing" let toolbarItemIcon = NSImage(named: NSImage.networkName)! + let tableViewDragType: String = "v2ray.routing" @IBOutlet weak var domainStrategy: NSPopUpButton! - @IBOutlet weak var routingRule: NSPopUpButton! - @IBOutlet var proxyTextView: NSTextView! - @IBOutlet var directTextView: NSTextView! - @IBOutlet var blockTextView: NSTextView! - + @IBOutlet weak var proxyTextView: NSTextView! + @IBOutlet weak var directTextView: NSTextView! + @IBOutlet weak var blockTextView: NSTextView! + @IBOutlet weak var routingRuleContent: NSTextView! + @IBOutlet weak var routingRuleName: NSTextField! + @IBOutlet weak var defaulRoutingRuleName: NSTextField! + @IBOutlet weak var errTip: NSTextField! + @IBOutlet weak var routingsTableView: NSTableView! + @IBOutlet weak var addRemoveButton: NSSegmentedControl! + @IBOutlet weak var customView: NSView! + @IBOutlet weak var defaultView: NSView! + override var nibName: NSNib.Name? { return "PreferenceRouting" } @@ -29,30 +37,115 @@ final class PreferenceRoutingViewController: NSViewController, PreferencePane { super.viewDidLoad() // fix: https://github.com/sindresorhus/Preferences/issues/31 self.preferredContentSize = NSMakeSize(self.view.frame.size.width, self.view.frame.size.height); + // set table drag style + self.routingsTableView.registerForDraggedTypes([NSPasteboard.PasteboardType(rawValue: tableViewDragType)]) + self.routingsTableView.allowsMultipleSelection = true - let domainStrategy = UserDefaults.get(forKey: .routingDomainStrategy) ?? "AsIs" - self.domainStrategy.selectItem(withTitle: domainStrategy) - - let routingRule = Int(UserDefaults.get(forKey: .routingRule) ?? "0") ?? 0 - self.routingRule.selectItem(withTag: routingRule) - - let routingProxyDomains = UserDefaults.getArray(forKey: .routingProxyDomains) ?? []; - let routingProxyIps = UserDefaults.getArray(forKey: .routingProxyIps) ?? []; - let routingDirectDomains = UserDefaults.getArray(forKey: .routingDirectDomains) ?? []; - let routingDirectIps = UserDefaults.getArray(forKey: .routingDirectIps) ?? []; - let routingBlockDomains = UserDefaults.getArray(forKey: .routingBlockDomains) ?? []; - let routingBlockIps = UserDefaults.getArray(forKey: .routingBlockIps) ?? []; - - let routingProxy = routingProxyDomains + routingProxyIps - let routingDirect = routingDirectDomains + routingDirectIps - let routingBlock = routingBlockDomains + routingBlockIps - - print("routingProxy", routingProxy, routingDirect, routingBlock) - self.proxyTextView.string = routingProxy.joined(separator: "\n") - self.directTextView.string = routingDirect.joined(separator: "\n") - self.blockTextView.string = routingBlock.joined(separator: "\n") + // reload tableview + V2rayRoutings.loadConfig() + // table view + self.routingsTableView.delegate = self + self.routingsTableView.dataSource = self + self.routingsTableView.reloadData() + } + + func set_tip(str: String) { + self.errTip.stringValue = str + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + self.errTip.stringValue = "" + } + } + + func loadJsonData(rowIndex: Int) { + print("loadJsonData", rowIndex) + DispatchQueue.main.async { + self.defaultView.isHidden = true + self.customView.isHidden = true + if let item = V2rayRoutings.load(idx: rowIndex) { + let isDefaultRule = V2rayRoutings.isDefaultRule(name: item.name) + print("loadJsonData", item.domainStrategy,item.proxy,item.direct,item.block,item.json,isDefaultRule) + if isDefaultRule { + self.defaultView.isHidden = false + self.domainStrategy.selectItem(withTitle: item.domainStrategy) + self.proxyTextView.string = item.proxy + self.directTextView.string = item.direct + self.blockTextView.string = item.block + self.defaulRoutingRuleName.stringValue = item.remark + } else { + self.customView.isHidden = false + self.routingRuleName.stringValue = item.remark + self.routingRuleContent.string = item.json + } + } + } } + + @IBAction func addRemoveServer(_ sender: NSSegmentedCell) { + // 0 add,1 remove + let seg = addRemoveButton.indexOfSelectedItem + print("addRemoveServer",seg) + DispatchQueue.global().async { + switch seg { + // add server config + case 0: + // add + V2rayRoutings.add(remark: "new-rule", json: V2rayRoutings.default_rule_content) + + DispatchQueue.main.sync { + V2rayRoutings.loadConfig() + // reload data + self.routingsTableView.reloadData() + // selected current row + self.routingsTableView.selectRowIndexes(NSIndexSet(index: V2rayRoutings.count() - 1) as IndexSet, byExtendingSelection: false) + } + break + + // delete server config + case 1: + DispatchQueue.main.sync { + // get seleted index + let idx = self.routingsTableView.selectedRow + // remove + V2rayRoutings.remove(idx: idx) + + // reload + V2rayRoutings.loadConfig() + + // selected prev row + let cnt: Int = V2rayRoutings.count() + var rowIndex: Int = idx - 1 + if idx > 0 && idx < cnt { + rowIndex = idx + } + + // reload + self.routingsTableView.reloadData() + // fix + if cnt > 1 { + // selected row + self.routingsTableView.selectRowIndexes(NSIndexSet(index: rowIndex) as IndexSet, byExtendingSelection: false) + } + + if rowIndex >= 0 { + self.loadJsonData(rowIndex: rowIndex) + } else { + self.routingsTableView.becomeFirstResponder() + } + } + + // refresh menu + menuController.showRouting() + break + + // unknown action + default: + return + } + } + } + + @IBAction func goHelp(_ sender: Any) { guard let url = URL(string: "https://toutyrater.github.io/basic/routing/") else { return @@ -68,73 +161,144 @@ final class PreferenceRoutingViewController: NSViewController, PreferencePane { } @IBAction func saveRouting(_ sender: Any) { - UserDefaults.set(forKey: .routingDomainStrategy, value: self.domainStrategy.titleOfSelectedItem!) - UserDefaults.set(forKey: .routingRule, value: String(self.routingRule.selectedTag())) + let selectedRule = self.routingsTableView.selectedRow + if selectedRule == -1 { + return + } + guard let rule = V2rayRoutings.load(idx: selectedRule) else { + return + } - var (domains, ips) = self.parseDomainOrIp(domainIpStr: self.proxyTextView.string) - UserDefaults.setArray(forKey: .routingProxyDomains, value: domains) - UserDefaults.setArray(forKey: .routingProxyIps, value: ips) - - (domains, ips) = self.parseDomainOrIp(domainIpStr: self.directTextView.string) - UserDefaults.setArray(forKey: .routingDirectDomains, value: domains) - UserDefaults.setArray(forKey: .routingDirectIps, value: ips) - - (domains, ips) = self.parseDomainOrIp(domainIpStr: self.blockTextView.string) - UserDefaults.setArray(forKey: .routingBlockDomains, value: domains) - UserDefaults.setArray(forKey: .routingBlockIps, value: ips) + if V2rayRoutings.isDefaultRule(name: rule.name) { + let domainStrategy = self.domainStrategy.titleOfSelectedItem + if domainStrategy != nil { + rule.domainStrategy = domainStrategy! + } + rule.proxy = self.proxyTextView.string + rule.direct = self.directTextView.string + rule.block = self.blockTextView.string + } else { + rule.remark = self.routingRuleName.stringValue + let (res, err) = parseRoutingRuleJson(json: self.routingRuleContent.string) + if err != nil { + print("parseRoutingRuleJson err", err) + self.errTip.stringValue = "parse json err: \(err)" + // hide err + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + self.errTip.stringValue = "" + } + return + } else { + self.errTip.stringValue = "" + rule.json = self.routingRuleContent.string + } + } + // save update + V2rayRoutings.save(routing: rule) + + // reload table + self.routingsTableView.reloadData() + // set selected + self.routingsTableView.selectRowIndexes(NSIndexSet(index: selectedRule) as IndexSet, byExtendingSelection: false) // 更新菜单 - menuController.showRouring(); + menuController.showRouting(); + // if is selected rule name == rule.name, restart v2ray + let selectedRuleName = UserDefaults.get(forKey: .routingSelectedRule) + if selectedRuleName == rule.name { + // restart v2ray + V2rayLaunch.restartV2ray() + } + } +} - // set current server item and reload v2ray-core - V2rayLaunch.restartV2ray() +// NSnameSource +extension PreferenceRoutingViewController: NSTableViewDataSource { + + func numberOfRows(in tableView: NSTableView) -> Int { + return V2rayRoutings.count() } - func parseDomainOrIp(domainIpStr: String) -> (domains: [String], ips: [String]) { - let all = domainIpStr.split(separator: "\n") + // show cell + func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { + let v2rayItemList = V2rayRoutings.list() + // set cell data + if v2rayItemList.count >= row { + return v2rayItemList[row].remark + } + return nil + } +} - var domains: [String] = [] - var ips: [String] = [] +// NSTableViewDelegate +extension PreferenceRoutingViewController: NSTableViewDelegate { + // For NSTableViewDelegate + func tableViewSelectionDidChange(_ notification: Notification) { + // Ensure there's a valid selected row before calling loadJsonData + if self.routingsTableView.selectedRow >= 0 { + self.loadJsonData(rowIndex: self.routingsTableView.selectedRow) + } else { + // Handle the case where no row is selected or add a default behavior + print("No row selected") + } + self.errTip.stringValue = "" + } - for item in all { - let tmp = item.trimmingCharacters(in: .whitespacesAndNewlines) + // Drag & Drop reorder rows + func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? { + let item = NSPasteboardItem() + item.setString(String(row), forType: NSPasteboard.PasteboardType(rawValue: tableViewDragType)) + return item + } - // is ip - if isIp(str: tmp) || tmp.contains("geoip:") { - ips.append(tmp) - continue + func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation { + if dropOperation == .above { + return .move + } + return NSDragOperation() + } + + func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool { + var oldIndexes = [Int]() + info.enumerateDraggingItems(options: [], for: tableView, classes: [NSPasteboardItem.self], searchOptions: [:], using: { + (draggingItem: NSDraggingItem, idx: Int, stop: UnsafeMutablePointer) in + if let str = (draggingItem.item as! NSPasteboardItem).string(forType: NSPasteboard.PasteboardType(rawValue: self.tableViewDragType)), + let index = Int(str) { + oldIndexes.append(index) } + }) - // is domain - if tmp.contains("domain:") || tmp.contains("geosite:") { - domains.append(tmp) - continue - } + var oldIndexOffset = 0 + var newIndexOffset = 0 + var oldIndexLast = 0 + var newIndexLast = 0 - if isDomain(str: tmp) { - domains.append(tmp) - continue + // For simplicity, the code below uses `tableView.moveRowAtIndex` to move rows around directly. + // You may want to move rows in your content array and then call `tableView.reloadData()` instead. + for oldIndex in oldIndexes { + if oldIndex < row { + oldIndexLast = oldIndex + oldIndexOffset + newIndexLast = row - 1 + oldIndexOffset -= 1 + } else { + oldIndexLast = oldIndex + newIndexLast = row + newIndexOffset + newIndexOffset += 1 } } - - print("ips", ips, "domains", domains) - - return (domains, ips) - } - - func isIp(str: String) -> Bool { - let pattern = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(/[0-9]{2})?$" - if ((str.count == 0) || (str.range(of: pattern, options: .regularExpression) == nil)) { - return false - } - return true - } - - func isDomain(str: String) -> Bool { - let pattern = "[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+" - if ((str.count == 0) || (str.range(of: pattern, options: .regularExpression) == nil)) { - return false + DispatchQueue.global().async { + print("move",oldIndexLast,newIndexLast) + // move + V2rayRoutings.move(oldIndex: oldIndexLast, newIndex: newIndexLast) + DispatchQueue.main.async { + // set selected + self.routingsTableView.selectRowIndexes(NSIndexSet(index: newIndexLast) as IndexSet, byExtendingSelection: false) + // reload table + self.routingsTableView.reloadData() + } + // reload menu + menuController.showRouting() } return true } diff --git a/v2rayu/V2rayU/Util.swift b/v2rayu/V2rayU/Util.swift index 91234a91fb..40252c9e98 100644 --- a/v2rayu/V2rayU/Util.swift +++ b/v2rayu/V2rayU/Util.swift @@ -63,16 +63,10 @@ extension UserDefaults { // pacPort case localPacPort - // for routing rule - case routingDomainStrategy - case routingRule - case routingProxyDomains - case routingProxyIps - case routingDirectDomains - case routingDirectIps - case routingBlockDomains - case routingBlockIps - case Exception + // custom routing list + case routingCustomList + // routing selected rule + case routingSelectedRule } static func setBool(forKey key: KEY, value: Bool) { diff --git a/v2rayu/V2rayU/V2rayRouting.swift b/v2rayu/V2rayU/V2rayRouting.swift new file mode 100644 index 0000000000..c513afb61e --- /dev/null +++ b/v2rayu/V2rayU/V2rayRouting.swift @@ -0,0 +1,519 @@ +// +// V2rayRouting.swift +// V2rayU +// +// Created by yanue on 2024/6/27. +// Copyright © 2024 yanue. All rights reserved. +// + +import Foundation + +let RoutingRuleGlobal = "routing.global" +let RoutingRuleLAN = "routing.lan" +let RoutingRuleCn = "routing.cn" +let RoutingRuleLANAndCn = "routing.lanAndCn" + +let defaultRuleCn = Dictionary(uniqueKeysWithValues: [ + (RoutingRuleGlobal, "🌏全局"), + (RoutingRuleLAN, "🌏 绕过局域网"), + (RoutingRuleCn, "🌏 绕过中国大陆"), + (RoutingRuleLANAndCn, "🌏 绕过局域网和中国大陆") +]) + +let defaultRuleEn = Dictionary(uniqueKeysWithValues: [ + (RoutingRuleGlobal, "🌏 Global"), + (RoutingRuleLAN, "🌏 Bypassing the LAN Address"), + (RoutingRuleCn, "🌏 Bypassing mainland address"), + (RoutingRuleLANAndCn, "🌏 Bypassing LAN and mainland address") +]) + +let defaultRules = Dictionary(uniqueKeysWithValues: [ + (RoutingRuleGlobal,RoutingItem(name: RoutingRuleGlobal, remark: "")), + (RoutingRuleLAN,RoutingItem(name: RoutingRuleLAN, remark: "")), + (RoutingRuleCn,RoutingItem(name: RoutingRuleCn, remark: "")), + (RoutingRuleLANAndCn,RoutingItem(name: RoutingRuleLANAndCn,remark:"")) +]) + +// ----- routing server manager ----- +class V2rayRoutings: NSObject { + static var shared = V2rayRoutings() + + static let lock = NSLock() + + static let default_rule_content = """ +{ + "domainStrategy": "AsIs", + "rules": [ + ] +} +""" + // Initialization + override init() { + super.init() + V2rayRoutings.loadConfig() + } + + // routing server list + static private var routings: [RoutingItem] = [] + + // (init) load routing server list from UserDefaults + static func loadConfig() { + self.lock.lock() + defer { + self.lock.unlock() + } + + // static reset + self.routings = [] + + // load name list from UserDefaults + var list = UserDefaults.getArray(forKey: .routingCustomList) ?? []; + + print("V2rayRoutings-loadConfig", list) + + let langStr = Locale.current.languageCode + let isMainland = langStr == "zh-CN" || langStr == "zh" || langStr == "zh-Hans" || langStr == "zh-Hant" + // for defaultRules + for (key, rule) in defaultRules { + // load and check + if nil == RoutingItem.load(name: key) { + if isMainland { + rule.remark = defaultRuleCn[key] ?? rule.remark + } else { + rule.remark = defaultRuleEn[key] ?? rule.remark + } + // create new + rule.store() + } + // if not in list, append + if !list.contains(key) { + list.append(key) + } + } + + // load each RoutingItem + for item in list { + guard let routing = RoutingItem.load(name: item) else { + // delete from UserDefaults + RoutingItem.remove(name: item) + continue + } + // append + self.routings.append(routing) + } + print("V2rayRoutings-loadConfig", self.routings.count) + } + + static func isDefaultRule(name: String) -> Bool { + return defaultRules.keys.contains(name) + } + + // get list from routing server list + static func list() -> [RoutingItem] { + return self.routings + } + + static func all() -> [RoutingItem] { + // static reset + var items : [RoutingItem] = [] + + // load name list from UserDefaults + var list = UserDefaults.getArray(forKey: .routingCustomList) + if list == nil { + list = [] + } + + // load each V2rayItem + for item in list! { + guard let routing = RoutingItem.load(name: item) else { + // delete from UserDefaults + RoutingItem.remove(name: item) + continue + } + // append + items.append(routing) + } + return items + } + + // get count from routing server list + static func count() -> Int { + return self.routings.count + } + + // move item to new index + static func move(oldIndex: Int, newIndex: Int) { + if !V2rayRoutings.routings.indices.contains(oldIndex) { + NSLog("index out of range", oldIndex) + return + } + if !V2rayRoutings.routings.indices.contains(newIndex) { + NSLog("index out of range", newIndex) + return + } + + let o = self.routings[oldIndex] + self.routings.remove(at: oldIndex) + self.routings.insert(o, at: newIndex) + + // update server list UserDefaults + self.saveItemList() + } + + // add routing server (by scan qrcode) + static func add(remark: String, json: String) { + var remark_ = remark + if remark.count == 0 { + remark_ = "new routing" + } + + // name is : routing. + uuid + let name = "routing." + UUID().uuidString + + let routing = RoutingItem(name: name, remark: remark_, json: json) + // save to routing UserDefaults + routing.store() + + + // just add to mem + self.routings.append(routing) + print("routing", name, self.routings) + + // update server list UserDefaults + self.saveItemList() + } + + // remove routing server (tmp and UserDefaults and config json file) + static func remove(idx: Int) { + if !V2rayRoutings.routings.indices.contains(idx) { + NSLog("index out of range", idx) + return + } + + let routing = V2rayRoutings.routings[idx] + + // delete from tmp + self.routings.remove(at: idx) + + // delete from routing UserDefaults + RoutingItem.remove(name: routing.name) + + // update server list UserDefaults + self.saveItemList() + + // if cuerrent item is default + let curName = UserDefaults.get(forKey: .routingSelectedRule) + if curName != nil && routing.name == curName { + UserDefaults.del(forKey: .routingSelectedRule) + } + } + + // update server list UserDefaults + static func saveItemList() { + var routingCustomList: Array = [] + for item in V2rayRoutings.list() { + routingCustomList.append(item.name) + } + + print("routingCustomList", routingCustomList); + UserDefaults.setArray(forKey: .routingCustomList, value: routingCustomList) + } + + // load json file data + static func load(idx: Int) -> RoutingItem? { + if !V2rayRoutings.routings.indices.contains(idx) { + NSLog("index out of range", idx) + return nil + } + + return self.routings[idx] + } + + static func save(routing: RoutingItem) { + // store + routing.store() + + // refresh data + for (idx, item) in self.routings.enumerated() { + if item.name == routing.name { + self.routings[idx].remark = routing.remark + break + } + } + } + + // get by name + static func getIndex(name: String) -> Int { + for (idx, item) in self.routings.enumerated() { + if item.name == name { + return idx + } + } + return -1 + } +} + +// ----- routing routing item ----- +class RoutingItem: NSObject, NSCoding { + var name: String + var remark: String + var json: String + var domainStrategy: String + var block: String + var proxy: String + var direct: String + + // Initializer + init(name: String, remark: String, json: String = "", domainStrategy: String = "AsIs", block: String="", proxy: String="", direct: String="") { + self.name = name + self.remark = remark + self.json = json + self.domainStrategy = domainStrategy + self.block = block + self.proxy = proxy + self.direct = direct + } + + // NSCoding required initializer (decoding) + required init(coder decoder: NSCoder) { + self.name = decoder.decodeObject(forKey: "Name") as? String ?? "" + self.remark = decoder.decodeObject(forKey: "Remark") as? String ?? "" + self.json = decoder.decodeObject(forKey: "Json") as? String ?? "" + self.domainStrategy = decoder.decodeObject(forKey: "DomainStrategy") as? String ?? "AsIs" + self.block = decoder.decodeObject(forKey: "Block") as? String ?? "" + self.proxy = decoder.decodeObject(forKey: "Proxy") as? String ?? "" + self.direct = decoder.decodeObject(forKey: "Direct") as? String ?? "" + } + + // NSCoding required method (encoding) + func encode(with coder: NSCoder) { + coder.encode(name, forKey: "Name") + coder.encode(remark, forKey: "Remark") + coder.encode(json, forKey: "Json") + coder.encode(domainStrategy, forKey: "DomainStrategy") + coder.encode(block, forKey: "Block") + coder.encode(proxy, forKey: "Proxy") + coder.encode(direct, forKey: "Direct") + } + + // Store into UserDefaults + func store() { + let modelData = NSKeyedArchiver.archivedData(withRootObject: self) + UserDefaults.standard.set(modelData, forKey: self.name) + } + + func parseRule() -> V2rayRouting { + if defaultRules.keys.contains(self.name) { + return self.parseDefaultSettings() + } + let (res, err) = parseRoutingRuleJson(json: self.json) + if err != nil { + print("parseRule err", err) + } + return res + } + + // parse default settings + func parseDefaultSettings() -> V2rayRouting { + + var rules: [V2rayRoutingSettingRule] = [] + + let (blockDomains, blockIps) = parseDomainOrIp(domainIpStr: self.block) + let (proxyDomains, proxyIps) = parseDomainOrIp(domainIpStr: self.proxy) + let (directDomains, directIps) = parseDomainOrIp(domainIpStr: self.direct) + + // // rules + var ruleProxyDomain, ruleProxyIp, ruleDirectDomain, ruleDirectIp, ruleBlockDomain, ruleBlockIp, ruleDirectIpDefault, ruleDirectDomainDefault: V2rayRoutingSettingRule? + // proxy + if proxyDomains.count > 0 { + ruleProxyDomain = getRoutingRule(outTag: "proxy", domain: proxyDomains, ip: nil, port: nil) + } + if proxyIps.count > 0 { + ruleProxyIp = getRoutingRule(outTag: "proxy", domain: nil, ip: proxyIps, port: nil) + } + + // direct + if directDomains.count > 0 { + ruleDirectDomain = getRoutingRule(outTag: "direct", domain: directDomains, ip: nil, port: nil) + } + if directIps.count > 0 { + ruleDirectIp = getRoutingRule(outTag: "direct", domain: nil, ip: directIps, port: nil) + } + + // block + if blockDomains.count > 0 { + ruleBlockDomain = getRoutingRule(outTag: "block", domain: blockDomains, ip: nil, port: nil) + } + if blockIps.count > 0 { + ruleBlockIp = getRoutingRule(outTag: "block", domain: nil, ip: blockIps, port: nil) + } + + switch self.name { + case RoutingRuleGlobal: + break + case RoutingRuleLAN: + ruleDirectIpDefault = getRoutingRule(outTag: "direct", domain: nil, ip: ["geoip:private"], port: nil) + ruleDirectDomainDefault = getRoutingRule(outTag: "direct", domain: ["localhost"], ip: nil, port: nil) + break + case RoutingRuleCn: + ruleDirectIpDefault = getRoutingRule(outTag: "direct", domain: nil, ip: ["geoip:cn"], port: nil) + ruleDirectDomainDefault = getRoutingRule(outTag: "direct", domain: ["geosite:cn"], ip: nil, port: nil) + break + case RoutingRuleLANAndCn: + ruleDirectIpDefault = getRoutingRule(outTag: "direct", domain: nil, ip: ["geoip:cn","geoip:private"], port: nil) + ruleDirectDomainDefault = getRoutingRule(outTag: "direct", domain: ["geosite:cn","localhost"], ip: nil, port: nil) + break + default: break + + } + // 域名阻断 -> 域名代理 -> 域名直连 -> IP阻断 -> IP代理 -> IP直连 的优先级进行匹配 + + // 域名阻断 + if ruleBlockDomain != nil { + ruleBlockDomain?.ip = nil + rules.append(ruleBlockDomain!) + } + // 域名代理 + if ruleProxyDomain != nil { + ruleProxyDomain?.ip = nil + rules.append(ruleProxyDomain!) + } + // 域名直连 + if ruleDirectDomain != nil { + ruleDirectDomain!.ip = nil + rules.append(ruleDirectDomain!) + } + // IP阻断 + if ruleBlockIp != nil { + ruleBlockIp!.domain = nil + rules.append(ruleBlockIp!) + } + // IP代理 + if ruleProxyIp != nil { + ruleProxyIp!.domain = nil + rules.append(ruleProxyIp!) + } + // IP直连 + if ruleDirectIp != nil { + ruleDirectIp!.domain = nil + rules.append(ruleDirectIp!) + } + // 如果匹配失败,则私有地址和大陆境内地址直连,否则走代理。 + if ruleDirectIpDefault != nil { + ruleDirectIpDefault!.domain = nil + rules.append(ruleDirectIpDefault!) + } + if ruleDirectDomainDefault != nil { + ruleDirectDomainDefault!.ip = nil + rules.append(ruleDirectDomainDefault!) + } + // 默认全部代理, 无需设置规则 + var settings = V2rayRouting() + if V2rayRouting.domainStrategy(rawValue: self.domainStrategy) == nil { + settings.domainStrategy = .AsIs + } else { + settings.domainStrategy = V2rayRouting.domainStrategy(rawValue: self.domainStrategy) ?? .AsIs + } + settings.rules = rules + return settings + } + + func getRoutingRule(outTag: String, domain:[String]?, ip: [String]?, port:String?) -> V2rayRoutingSettingRule { + var rule = V2rayRoutingSettingRule() + rule.outboundTag = outTag + rule.type = "field" + rule.domain = domain + rule.ip = ip + rule.port = port + return rule + } + + func parseDomainOrIp(domainIpStr: String) -> (domains: [String], ips: [String]) { + let all = domainIpStr.split(separator: "\n") + + var domains: [String] = [] + var ips: [String] = [] + + for item in all { + let tmp = item.trimmingCharacters(in: .whitespacesAndNewlines) + + // is ip + if isIp(str: tmp) || tmp.contains("geoip:") { + ips.append(tmp) + continue + } + + // is domain + if tmp.contains("domain:") || tmp.contains("geosite:") { + domains.append(tmp) + continue + } + + if isDomain(str: tmp) { + domains.append(tmp) + continue + } + } + + print("ips", ips, "domains", domains) + + return (domains, ips) + } + + func isIp(str: String) -> Bool { + let pattern = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(/[0-9]{2})?$" + if ((str.count == 0) || (str.range(of: pattern, options: .regularExpression) == nil)) { + return false + } + return true + } + + func isDomain(str: String) -> Bool { + let pattern = "[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+" + if ((str.count == 0) || (str.range(of: pattern, options: .regularExpression) == nil)) { + return false + } + return true + } + + // static load from UserDefaults + static func load(name: String) -> RoutingItem? { + guard let myModelData = UserDefaults.standard.data(forKey: name) else { + print("load userDefault not found:",name) + return nil + } + do { + // unarchivedObject(ofClass:from:) + let result = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(myModelData) + return result as? RoutingItem + } catch let error { + print("load userDefault error:", error) + return nil + } + } + + // remove from UserDefaults + static func remove(name: String) { + UserDefaults.standard.removeObject(forKey: name) + } +} + + +// parse json to V2rayRouting +func parseRoutingRuleJson(json: String) -> (V2rayRouting, err: Error?) { + // utf8 + let jsonData = json.data(using: String.Encoding.utf8, allowLossyConversion: false) + if jsonData == nil { + return (V2rayRouting(), nil) + } + let jsonDecoder = JSONDecoder() + var res = V2rayRouting() + var err: Error? + do { + res = try jsonDecoder.decode(V2rayRouting.self, from: jsonData!) + } catch let error { + print("parseJson err",error) + err = error + } + return (res, err) +} diff --git a/v2rayu/V2rayU/v2ray/V2rayConfig.swift b/v2rayu/V2rayU/v2ray/V2rayConfig.swift index f952cbd033..55c4692a32 100644 --- a/v2rayu/V2rayU/v2ray/V2rayConfig.swift +++ b/v2rayu/V2rayU/v2ray/V2rayConfig.swift @@ -81,13 +81,6 @@ let jsSourceFormatConfig = class V2rayConfig: NSObject { - // routing rule tag - enum RoutingRule: Int { - case RoutingRuleGlobal = 0 // Global - case RoutingRuleLAN = 1 // Bypassing the LAN Address - case RoutingRuleCn = 2 // Bypassing mainland address - case RoutingRuleLANAndCn = 3 // Bypassing LAN and mainland address - } var v2ray: V2rayStruct = V2rayStruct() var isValid = false @@ -131,15 +124,6 @@ class V2rayConfig: NSObject { var streamSecurity = "none" // none|tls|xtls|reality var securityTls = TlsSettings() // tls|xtls var securityReality = RealitySettings() // reality - - var routingDomainStrategy: V2rayRoutingSetting.domainStrategy = .AsIs - var routingRule: RoutingRule = .RoutingRuleGlobal - let routingProxyDomains = UserDefaults.getArray(forKey: .routingProxyDomains) ?? []; - let routingProxyIps = UserDefaults.getArray(forKey: .routingProxyIps) ?? []; - let routingDirectDomains = UserDefaults.getArray(forKey: .routingDirectDomains) ?? []; - let routingDirectIps = UserDefaults.getArray(forKey: .routingDirectIps) ?? []; - let routingBlockDomains = UserDefaults.getArray(forKey: .routingBlockDomains) ?? []; - let routingBlockIps = UserDefaults.getArray(forKey: .routingBlockIps) ?? []; private var foundHttpPort = false private var foundSockPort = false @@ -161,10 +145,6 @@ class V2rayConfig: NSObject { self.mux = Int(UserDefaults.get(forKey: .muxConcurrent) ?? "8") ?? 8 self.logLevel = UserDefaults.get(forKey: .v2rayLogLevel) ?? "info" - - // routing - self.routingDomainStrategy = V2rayRoutingSetting.domainStrategy(rawValue: UserDefaults.get(forKey: .routingDomainStrategy) ?? "AsIs") ?? .AsIs - self.routingRule = RoutingRule(rawValue: Int(UserDefaults.get(forKey: .routingRule) ?? "0") ?? 0) ?? .RoutingRuleGlobal } // combine manual edited data @@ -292,112 +272,14 @@ class V2rayConfig: NSObject { // ------------------------------------- outbound end --------------------------------------------- // ------------------------------------- routing start -------------------------------------------- - - self.routing.settings.domainStrategy = self.routingDomainStrategy - var rules: [V2rayRoutingSettingRule] = [] - - // rules - var ruleProxyDomain, ruleProxyIp, ruleDirectDomain, ruleDirectIp, ruleBlockDomain, ruleBlockIp, ruleDirectIpDefault, ruleDirectDomainDefault: V2rayRoutingSettingRule? - print("ruleBlockDomain",self.routingBlockDomains) - // proxy - if self.routingProxyDomains.count > 0 { - ruleProxyDomain = getRoutingRule(outTag: "proxy", domain: self.routingProxyDomains, ip: nil, port: nil) + let routingRule = UserDefaults.get(forKey: .routingSelectedRule) ?? RoutingRuleGlobal + let rule = RoutingItem.load(name: routingRule) + if rule != nil{ + self.v2ray.routing = rule!.parseRule() } - if self.routingProxyIps.count > 0 { - ruleProxyIp = getRoutingRule(outTag: "proxy", domain: nil, ip: self.routingProxyIps, port: nil) - } - - // direct - if self.routingDirectDomains.count > 0 { - ruleDirectDomain = getRoutingRule(outTag: "direct", domain: self.routingDirectDomains, ip: nil, port: nil) - } - if self.routingDirectIps.count > 0 { - ruleDirectIp = getRoutingRule(outTag: "direct", domain: nil, ip: self.routingDirectIps, port: nil) - } - - // block - if self.routingBlockDomains.count > 0 { - ruleBlockDomain = getRoutingRule(outTag: "block", domain: self.routingBlockDomains, ip: nil, port: nil) - } - if self.routingBlockIps.count > 0 { - ruleBlockIp = getRoutingRule(outTag: "block", domain: nil, ip: self.routingBlockIps, port: nil) - } - - switch self.routingRule { - case .RoutingRuleGlobal: - break - case .RoutingRuleLAN: - ruleDirectIpDefault = getRoutingRule(outTag: "direct", domain: nil, ip: ["geoip:private"], port: nil) - ruleDirectDomainDefault = getRoutingRule(outTag: "direct", domain: ["localhost"], ip: nil, port: nil) - break - case .RoutingRuleCn: - ruleDirectIpDefault = getRoutingRule(outTag: "direct", domain: nil, ip: ["geoip:cn"], port: nil) - ruleDirectDomainDefault = getRoutingRule(outTag: "direct", domain: ["geosite:cn"], ip: nil, port: nil) - break - case .RoutingRuleLANAndCn: - ruleDirectIpDefault = getRoutingRule(outTag: "direct", domain: nil, ip: ["geoip:cn","geoip:private"], port: nil) - ruleDirectDomainDefault = getRoutingRule(outTag: "direct", domain: ["geosite:cn","localhost"], ip: nil, port: nil) - break - } - // 域名阻断 -> 域名代理 -> 域名直连 -> IP阻断 -> IP代理 -> IP直连 的优先级进行匹配 - - // 域名阻断 - if ruleBlockDomain != nil { - ruleBlockDomain?.ip = nil - rules.append(ruleBlockDomain!) - } - // 域名代理 - if ruleProxyDomain != nil { - ruleProxyDomain?.ip = nil - rules.append(ruleProxyDomain!) - } - // 域名直连 - if ruleDirectDomain != nil { - ruleDirectDomain!.ip = nil - rules.append(ruleDirectDomain!) - } - // IP阻断 - if ruleBlockIp != nil { - ruleBlockIp!.domain = nil - rules.append(ruleBlockIp!) - } - // IP代理 - if ruleProxyIp != nil { - ruleProxyIp!.domain = nil - rules.append(ruleProxyIp!) - } - // IP直连 - if ruleDirectIp != nil { - ruleDirectIp!.domain = nil - rules.append(ruleDirectIp!) - } - // 如果匹配失败,则私有地址和大陆境内地址直连,否则走代理。 - if ruleDirectIpDefault != nil { - ruleDirectIpDefault!.domain = nil - rules.append(ruleDirectIpDefault!) - } - if ruleDirectDomainDefault != nil { - ruleDirectDomainDefault!.ip = nil - rules.append(ruleDirectDomainDefault!) - } - // 默认全部代理, 无需设置规则 - // 代理规则 - self.routing.settings.rules = rules - // set v2ray routing - self.v2ray.routing = self.routing // ------------------------------------- routing end ---------------------------------------------- } - func getRoutingRule(outTag: String, domain:[String]?, ip: [String]?, port:String?) -> V2rayRoutingSettingRule { - var rule = V2rayRoutingSettingRule() - rule.outboundTag = outTag - rule.type = "field" - rule.domain = domain - rule.ip = ip - rule.port = port - return rule - } - func checkManualValid() { defer { if self.error != "" { diff --git a/v2rayu/V2rayU/v2ray/v2rayStruct.swift b/v2rayu/V2rayU/v2ray/v2rayStruct.swift index cc2a72c3b0..2875399045 100644 --- a/v2rayu/V2rayU/v2ray/v2rayStruct.swift +++ b/v2rayu/V2rayU/v2ray/v2rayStruct.swift @@ -60,11 +60,6 @@ struct V2rayStats: Codable { } struct V2rayRouting: Codable { -// var strategy: String = "rules" - var settings: V2rayRoutingSetting = V2rayRoutingSetting() -} - -struct V2rayRoutingSetting: Codable { enum domainStrategy: String, Codable { case AsIs case IPIfNonMatch diff --git a/yt-dlp/pyproject.toml b/yt-dlp/pyproject.toml index 01162b794c..a2442a14d5 100644 --- a/yt-dlp/pyproject.toml +++ b/yt-dlp/pyproject.toml @@ -72,7 +72,7 @@ dev = [ ] static-analysis = [ "autopep8~=2.0", - "ruff~=0.4.4", + "ruff~=0.5.0", ] test = [ "pytest~=8.1", @@ -211,6 +211,7 @@ ignore = [ "TD002", # missing-todo-author "TD003", # missing-todo-link "PLE0604", # invalid-all-object (false positives) + "PLE0643", # potential-index-error (false positives) "PLW0603", # global-statement "PLW1510", # subprocess-run-without-check "PLW2901", # redefined-loop-name diff --git a/yt-dlp/yt_dlp/extractor/atresplayer.py b/yt-dlp/yt_dlp/extractor/atresplayer.py index 7c8139714f..0fe95bec5c 100644 --- a/yt-dlp/yt_dlp/extractor/atresplayer.py +++ b/yt-dlp/yt_dlp/extractor/atresplayer.py @@ -33,14 +33,6 @@ class AtresPlayerIE(InfoExtractor): ] _API_BASE = 'https://api.atresplayer.com/' - def _handle_error(self, e, code): - if isinstance(e.cause, HTTPError) and e.cause.status == code: - error = self._parse_json(e.cause.response.read(), None) - if error.get('error') == 'required_registered': - self.raise_login_required() - raise ExtractorError(error['error_description'], expected=True) - raise - def _perform_login(self, username, password): self._request_webpage( self._API_BASE + 'login', None, 'Downloading login page') @@ -55,7 +47,9 @@ class AtresPlayerIE(InfoExtractor): 'password': password, }))['targetUrl'] except ExtractorError as e: - self._handle_error(e, 400) + if isinstance(e.cause, HTTPError) and e.cause.status == 400: + raise ExtractorError('Invalid username and/or password', expected=True) + raise self._request_webpage(target_url, None, 'Following Target URL') @@ -66,7 +60,12 @@ class AtresPlayerIE(InfoExtractor): episode = self._download_json( self._API_BASE + 'client/v1/player/episode/' + video_id, video_id) except ExtractorError as e: - self._handle_error(e, 403) + if isinstance(e.cause, HTTPError) and e.cause.status == 403: + error = self._parse_json(e.cause.response.read(), None) + if error.get('error') == 'required_registered': + self.raise_login_required() + raise ExtractorError(error['error_description'], expected=True) + raise title = episode['titulo'] diff --git a/yt-dlp/yt_dlp/extractor/bitchute.py b/yt-dlp/yt_dlp/extractor/bitchute.py index c74f34c2a9..c83222ea5b 100644 --- a/yt-dlp/yt_dlp/extractor/bitchute.py +++ b/yt-dlp/yt_dlp/extractor/bitchute.py @@ -24,7 +24,7 @@ from ..utils import ( class BitChuteIE(InfoExtractor): - _VALID_URL = r'https?://(?:www\.)?bitchute\.com/(?:video|embed|torrent/[^/]+)/(?P[^/?#&]+)' + _VALID_URL = r'https?://(?:(?:www|old)\.)?bitchute\.com/(?:video|embed|torrent/[^/]+)/(?P[^/?#&]+)' _EMBED_REGEX = [rf'<(?:script|iframe)[^>]+\bsrc=(["\'])(?P{_VALID_URL})'] _TESTS = [{ 'url': 'https://www.bitchute.com/video/UGlrF9o9b-Q/', @@ -91,6 +91,9 @@ class BitChuteIE(InfoExtractor): }, { 'url': 'https://www.bitchute.com/torrent/Zee5BE49045h/szoMrox2JEI.webtorrent', 'only_matching': True, + }, { + 'url': 'https://old.bitchute.com/video/UGlrF9o9b-Q/', + 'only_matching': True, }] _GEO_BYPASS = False @@ -132,7 +135,7 @@ class BitChuteIE(InfoExtractor): def _real_extract(self, url): video_id = self._match_id(url) webpage = self._download_webpage( - f'https://www.bitchute.com/video/{video_id}', video_id, headers=self._HEADERS) + f'https://old.bitchute.com/video/{video_id}', video_id, headers=self._HEADERS) self._raise_if_restricted(webpage) publish_date = clean_html(get_element_by_class('video-publish-date', webpage)) @@ -171,13 +174,13 @@ class BitChuteIE(InfoExtractor): class BitChuteChannelIE(InfoExtractor): - _VALID_URL = r'https?://(?:www\.)?bitchute\.com/(?Pchannel|playlist)/(?P[^/?#&]+)' + _VALID_URL = r'https?://(?:(?:www|old)\.)?bitchute\.com/(?Pchannel|playlist)/(?P[^/?#&]+)' _TESTS = [{ 'url': 'https://www.bitchute.com/channel/bitchute/', 'info_dict': { 'id': 'bitchute', 'title': 'BitChute', - 'description': 'md5:5329fb3866125afa9446835594a9b138', + 'description': 'md5:2134c37d64fc3a4846787c402956adac', }, 'playlist': [ { @@ -210,6 +213,9 @@ class BitChuteChannelIE(InfoExtractor): 'title': 'Bruce MacDonald and "The Light of Darkness"', 'description': 'md5:747724ef404eebdfc04277714f81863e', }, + }, { + 'url': 'https://old.bitchute.com/playlist/wV9Imujxasw9/', + 'only_matching': True, }] _TOKEN = 'zyG6tQcGPE5swyAEFLqKUwMuMMuF6IO2DZ6ZDQjGfsL0e4dcTLwqkTTul05Jdve7' @@ -230,7 +236,7 @@ class BitChuteChannelIE(InfoExtractor): @staticmethod def _make_url(playlist_id, playlist_type): - return f'https://www.bitchute.com/{playlist_type}/{playlist_id}/' + return f'https://old.bitchute.com/{playlist_type}/{playlist_id}/' def _fetch_page(self, playlist_id, playlist_type, page_num): playlist_url = self._make_url(playlist_id, playlist_type) diff --git a/yt-dlp/yt_dlp/extractor/cbc.py b/yt-dlp/yt_dlp/extractor/cbc.py index 740e129264..1522b08e25 100644 --- a/yt-dlp/yt_dlp/extractor/cbc.py +++ b/yt-dlp/yt_dlp/extractor/cbc.py @@ -455,10 +455,8 @@ class CBCGemIE(InfoExtractor): def claims_token_expired(self): exp = self._get_claims_token_expiry() - if exp - time.time() < 10: - # It will expire in less than 10 seconds, or has already expired - return True - return False + # It will expire in less than 10 seconds, or has already expired + return exp - time.time() < 10 def claims_token_valid(self): return self._claims_token is not None and not self.claims_token_expired() diff --git a/yt-dlp/yt_dlp/extractor/mlb.py b/yt-dlp/yt_dlp/extractor/mlb.py index 8a693dc0be..6f67602a69 100644 --- a/yt-dlp/yt_dlp/extractor/mlb.py +++ b/yt-dlp/yt_dlp/extractor/mlb.py @@ -9,9 +9,10 @@ from ..utils import ( join_nonempty, parse_duration, parse_iso8601, - traverse_obj, try_get, + url_or_none, ) +from ..utils.traversal import traverse_obj class MLBBaseIE(InfoExtractor): @@ -326,15 +327,20 @@ class MLBTVIE(InfoExtractor): video_id)['data']['Airings'] formats, subtitles = [], {} - for airing in airings: - m3u8_url = self._download_json( + for airing in traverse_obj(airings, lambda _, v: v['playbackUrls'][0]['href']): + format_id = join_nonempty('feedType', 'feedLanguage', from_dict=airing) + m3u8_url = traverse_obj(self._download_json( airing['playbackUrls'][0]['href'].format(scenario='browser~csai'), video_id, - headers={ + note=f'Downloading {format_id} stream info JSON', + errnote=f'Failed to download {format_id} stream info, skipping', + fatal=False, headers={ 'Authorization': self._access_token, 'Accept': 'application/vnd.media-service+json; version=2', - })['stream']['complete'] + }), ('stream', 'complete', {url_or_none})) + if not m3u8_url: + continue f, s = self._extract_m3u8_formats_and_subtitles( - m3u8_url, video_id, 'mp4', m3u8_id=join_nonempty(airing.get('feedType'), airing.get('feedLanguage'))) + m3u8_url, video_id, 'mp4', m3u8_id=format_id, fatal=False) formats.extend(f) self._merge_subtitles(s, target=subtitles) diff --git a/yt-dlp/yt_dlp/jsinterp.py b/yt-dlp/yt_dlp/jsinterp.py index 5c82de19ea..a0f32892fd 100644 --- a/yt-dlp/yt_dlp/jsinterp.py +++ b/yt-dlp/yt_dlp/jsinterp.py @@ -667,12 +667,12 @@ class JSInterpreter: self.interpret_expression(v, local_vars, allow_recursion) for v in self._separate(arg_str)] - if obj == str: + if obj is str: if member == 'fromCharCode': assertion(argvals, 'takes one or more arguments') return ''.join(map(chr, argvals)) raise self.Exception(f'Unsupported String method {member}', expr) - elif obj == float: + elif obj is float: if member == 'pow': assertion(len(argvals) == 2, 'takes two arguments') return argvals[0] ** argvals[1] diff --git a/yt-dlp/yt_dlp/networking/_requests.py b/yt-dlp/yt_dlp/networking/_requests.py index c69c54b3a0..86850c1851 100644 --- a/yt-dlp/yt_dlp/networking/_requests.py +++ b/yt-dlp/yt_dlp/networking/_requests.py @@ -230,9 +230,7 @@ class Urllib3LoggingFilter(logging.Filter): def filter(self, record): # Ignore HTTP request messages since HTTPConnection prints those - if record.msg == '%s://%s:%s "%s %s %s" %s %s': - return False - return True + return record.msg != '%s://%s:%s "%s %s %s" %s %s' class Urllib3LoggingHandler(logging.Handler):