Update On Tue Mar 4 19:36:21 CET 2025

This commit is contained in:
github-action[bot]
2025-03-04 19:36:21 +01:00
parent bb2cbae785
commit 8bc751299e
102 changed files with 2790 additions and 2373 deletions

1
.github/update.log vendored
View File

@@ -931,3 +931,4 @@ Update On Fri Feb 28 19:34:20 CET 2025
Update On Sat Mar 1 19:31:26 CET 2025
Update On Sun Mar 2 19:33:02 CET 2025
Update On Mon Mar 3 19:34:32 CET 2025
Update On Tue Mar 4 19:36:12 CET 2025

File diff suppressed because it is too large Load Diff

View File

@@ -9247,9 +9247,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.139"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"indexmap 2.7.1",
"itoa 1.0.14",

View File

@@ -53,7 +53,7 @@
"@csstools/normalize.css": "12.1.1",
"@emotion/babel-plugin": "11.13.5",
"@emotion/react": "11.14.0",
"@iconify/json": "2.2.312",
"@iconify/json": "2.2.313",
"@monaco-editor/react": "4.7.0",
"@tanstack/react-query": "5.66.11",
"@tanstack/react-router": "1.112.0",

View File

@@ -1,11 +1,11 @@
{
"manifest_version": 1,
"latest": {
"mihomo": "v1.19.2",
"mihomo_alpha": "alpha-a7e56f1",
"mihomo": "v1.19.3",
"mihomo_alpha": "alpha-8bc6f77",
"clash_rs": "v0.7.6",
"clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.7.6-alpha+sha.14e7d32"
"clash_rs_alpha": "0.7.6-alpha+sha.167eccd"
},
"arch_template": {
"mihomo": {
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
}
},
"updated_at": "2025-03-02T22:21:03.218Z"
"updated_at": "2025-03-03T22:31:23.606Z"
}

View File

@@ -65,7 +65,7 @@
"@tauri-apps/cli": "2.2.7",
"@types/fs-extra": "11.0.4",
"@types/lodash-es": "4.17.12",
"@types/node": "22.13.8",
"@types/node": "22.13.9",
"@typescript-eslint/eslint-plugin": "8.25.0",
"@typescript-eslint/parser": "8.25.0",
"autoprefixer": "10.4.20",

View File

@@ -20,7 +20,7 @@ importers:
devDependencies:
'@commitlint/cli':
specifier: 19.7.1
version: 19.7.1(@types/node@22.13.8)(typescript@5.8.2)
version: 19.7.1(@types/node@22.13.9)(typescript@5.8.2)
'@commitlint/config-conventional':
specifier: 19.7.1
version: 19.7.1
@@ -43,8 +43,8 @@ importers:
specifier: 4.17.12
version: 4.17.12
'@types/node':
specifier: 22.13.8
version: 22.13.8
specifier: 22.13.9
version: 22.13.9
'@typescript-eslint/eslint-plugin':
specifier: 8.25.0
version: 8.25.0(@typescript-eslint/parser@8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)
@@ -104,7 +104,7 @@ importers:
version: 16.0.0
knip:
specifier: 5.45.0
version: 5.45.0(@types/node@22.13.8)(typescript@5.8.2)
version: 5.45.0(@types/node@22.13.9)(typescript@5.8.2)
lint-staged:
specifier: 15.4.3
version: 15.4.3
@@ -333,8 +333,8 @@ importers:
specifier: 11.14.0
version: 11.14.0(@types/react@19.0.10)(react@19.0.0)
'@iconify/json':
specifier: 2.2.312
version: 2.2.312
specifier: 2.2.313
version: 2.2.313
'@monaco-editor/react':
specifier: 4.7.0
version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@@ -349,7 +349,7 @@ importers:
version: 1.112.6(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@tanstack/router-plugin':
specifier: 1.112.3
version: 1.112.3(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
version: 1.112.3(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
'@tauri-apps/plugin-clipboard-manager':
specifier: 2.2.1
version: 2.2.1
@@ -385,13 +385,13 @@ importers:
version: 13.12.2
'@vitejs/plugin-legacy':
specifier: 6.0.2
version: 6.0.2(terser@5.36.0)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
version: 6.0.2(terser@5.36.0)(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
'@vitejs/plugin-react':
specifier: 4.3.4
version: 4.3.4(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
version: 4.3.4(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
'@vitejs/plugin-react-swc':
specifier: 3.8.0
version: 3.8.0(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
version: 3.8.0(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
change-case:
specifier: 5.4.4
version: 5.4.4
@@ -430,19 +430,19 @@ importers:
version: 13.12.0
vite:
specifier: 6.2.0
version: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
version: 6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vite-plugin-html:
specifier: 3.2.2
version: 3.2.2(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
version: 3.2.2(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
vite-plugin-sass-dts:
specifier: 1.3.30
version: 1.3.30(postcss@8.5.3)(prettier@3.5.2)(sass-embedded@1.85.1)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
version: 1.3.30(postcss@8.5.3)(prettier@3.5.2)(sass-embedded@1.85.1)(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
vite-plugin-svgr:
specifier: 4.3.0
version: 4.3.0(rollup@4.34.3)(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
version: 4.3.0(rollup@4.34.3)(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
vite-tsconfig-paths:
specifier: 5.1.4
version: 5.1.4(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
version: 5.1.4(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
zod:
specifier: 3.24.2
version: 3.24.2
@@ -478,7 +478,7 @@ importers:
version: 19.0.10
'@vitejs/plugin-react':
specifier: 4.3.4
version: 4.3.4(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
version: 4.3.4(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
ahooks:
specifier: 3.8.4
version: 3.8.4(react@19.0.0)
@@ -508,10 +508,10 @@ importers:
version: 4.0.9
vite:
specifier: 6.2.0
version: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
version: 6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vite-tsconfig-paths:
specifier: 5.1.4
version: 5.1.4(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
version: 5.1.4(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
devDependencies:
'@emotion/react':
specifier: 11.14.0
@@ -536,7 +536,7 @@ importers:
version: 5.1.0(typescript@5.8.2)
vite-plugin-dts:
specifier: 4.5.3
version: 4.5.3(@types/node@22.13.8)(rollup@4.34.3)(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
version: 4.5.3(@types/node@22.13.9)(rollup@4.34.3)(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
scripts:
dependencies:
@@ -1666,8 +1666,8 @@ packages:
'@vue/compiler-sfc':
optional: true
'@iconify/json@2.2.312':
resolution: {integrity: sha512-sujKsprCACPMVZHeNaxvc7HyONW916tUVw4zfEK+GmT7PkH73PdplmW1w2UWC07IC6oUYPZ2EE0pfdWHhWHjpQ==}
'@iconify/json@2.2.313':
resolution: {integrity: sha512-lzZ0KFpb0/IN4TVp/9lkUQQx6pmDqe6UD6lWiwkjnPMUVfFXYFMOfy6Qna5zb2cKwVQLTcOUgKk82Ah4qilKlw==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -3122,8 +3122,8 @@ packages:
'@types/node@22.10.1':
resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==}
'@types/node@22.13.8':
resolution: {integrity: sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==}
'@types/node@22.13.9':
resolution: {integrity: sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -9005,11 +9005,11 @@ snapshots:
'@bufbuild/protobuf@2.2.3': {}
'@commitlint/cli@19.7.1(@types/node@22.13.8)(typescript@5.8.2)':
'@commitlint/cli@19.7.1(@types/node@22.13.9)(typescript@5.8.2)':
dependencies:
'@commitlint/format': 19.5.0
'@commitlint/lint': 19.7.1
'@commitlint/load': 19.6.1(@types/node@22.13.8)(typescript@5.8.2)
'@commitlint/load': 19.6.1(@types/node@22.13.9)(typescript@5.8.2)
'@commitlint/read': 19.5.0
'@commitlint/types': 19.5.0
tinyexec: 0.3.1
@@ -9056,7 +9056,7 @@ snapshots:
'@commitlint/rules': 19.6.0
'@commitlint/types': 19.5.0
'@commitlint/load@19.6.1(@types/node@22.13.8)(typescript@5.8.2)':
'@commitlint/load@19.6.1(@types/node@22.13.9)(typescript@5.8.2)':
dependencies:
'@commitlint/config-validator': 19.5.0
'@commitlint/execute-rule': 19.5.0
@@ -9064,7 +9064,7 @@ snapshots:
'@commitlint/types': 19.5.0
chalk: 5.4.1
cosmiconfig: 9.0.0(typescript@5.8.2)
cosmiconfig-typescript-loader: 6.1.0(@types/node@22.13.8)(cosmiconfig@9.0.0(typescript@5.8.2))(typescript@5.8.2)
cosmiconfig-typescript-loader: 6.1.0(@types/node@22.13.9)(cosmiconfig@9.0.0(typescript@5.8.2))(typescript@5.8.2)
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
@@ -9426,7 +9426,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@iconify/json@2.2.312':
'@iconify/json@2.2.313':
dependencies:
'@iconify/types': 2.0.0
pathe: 1.1.2
@@ -9489,23 +9489,23 @@ snapshots:
'@material/material-color-utilities@0.3.0': {}
'@microsoft/api-extractor-model@7.30.3(@types/node@22.13.8)':
'@microsoft/api-extractor-model@7.30.3(@types/node@22.13.9)':
dependencies:
'@microsoft/tsdoc': 0.15.1
'@microsoft/tsdoc-config': 0.17.1
'@rushstack/node-core-library': 5.11.0(@types/node@22.13.8)
'@rushstack/node-core-library': 5.11.0(@types/node@22.13.9)
transitivePeerDependencies:
- '@types/node'
'@microsoft/api-extractor@7.51.0(@types/node@22.13.8)':
'@microsoft/api-extractor@7.51.0(@types/node@22.13.9)':
dependencies:
'@microsoft/api-extractor-model': 7.30.3(@types/node@22.13.8)
'@microsoft/api-extractor-model': 7.30.3(@types/node@22.13.9)
'@microsoft/tsdoc': 0.15.1
'@microsoft/tsdoc-config': 0.17.1
'@rushstack/node-core-library': 5.11.0(@types/node@22.13.8)
'@rushstack/node-core-library': 5.11.0(@types/node@22.13.9)
'@rushstack/rig-package': 0.5.3
'@rushstack/terminal': 0.15.0(@types/node@22.13.8)
'@rushstack/ts-command-line': 4.23.5(@types/node@22.13.8)
'@rushstack/terminal': 0.15.0(@types/node@22.13.9)
'@rushstack/ts-command-line': 4.23.5(@types/node@22.13.9)
lodash: 4.17.21
minimatch: 3.0.8
resolve: 1.22.8
@@ -10175,7 +10175,7 @@ snapshots:
'@rtsao/scc@1.1.0': {}
'@rushstack/node-core-library@5.11.0(@types/node@22.13.8)':
'@rushstack/node-core-library@5.11.0(@types/node@22.13.9)':
dependencies:
ajv: 8.13.0
ajv-draft-04: 1.0.0(ajv@8.13.0)
@@ -10186,23 +10186,23 @@ snapshots:
resolve: 1.22.8
semver: 7.5.4
optionalDependencies:
'@types/node': 22.13.8
'@types/node': 22.13.9
'@rushstack/rig-package@0.5.3':
dependencies:
resolve: 1.22.8
strip-json-comments: 3.1.1
'@rushstack/terminal@0.15.0(@types/node@22.13.8)':
'@rushstack/terminal@0.15.0(@types/node@22.13.9)':
dependencies:
'@rushstack/node-core-library': 5.11.0(@types/node@22.13.8)
'@rushstack/node-core-library': 5.11.0(@types/node@22.13.9)
supports-color: 8.1.1
optionalDependencies:
'@types/node': 22.13.8
'@types/node': 22.13.9
'@rushstack/ts-command-line@4.23.5(@types/node@22.13.8)':
'@rushstack/ts-command-line@4.23.5(@types/node@22.13.9)':
dependencies:
'@rushstack/terminal': 0.15.0(@types/node@22.13.8)
'@rushstack/terminal': 0.15.0(@types/node@22.13.9)
'@types/argparse': 1.0.38
argparse: 1.0.10
string-argv: 0.3.2
@@ -10519,7 +10519,7 @@ snapshots:
optionalDependencies:
'@tanstack/react-router': 1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@tanstack/router-plugin@1.112.3(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))':
'@tanstack/router-plugin@1.112.3(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))':
dependencies:
'@babel/core': 7.26.9
'@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.9)
@@ -10540,7 +10540,7 @@ snapshots:
zod: 3.24.2
optionalDependencies:
'@tanstack/react-router': 1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
vite: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vite: 6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
transitivePeerDependencies:
- supports-color
@@ -10693,12 +10693,12 @@ snapshots:
dependencies:
'@types/http-cache-semantics': 4.0.4
'@types/keyv': 3.1.4
'@types/node': 22.13.8
'@types/node': 22.13.9
'@types/responselike': 1.0.3
'@types/conventional-commits-parser@5.0.0':
dependencies:
'@types/node': 22.13.8
'@types/node': 22.13.9
'@types/d3-array@3.2.1': {}
@@ -10834,7 +10834,7 @@ snapshots:
'@types/fs-extra@11.0.4':
dependencies:
'@types/jsonfile': 6.1.4
'@types/node': 22.13.8
'@types/node': 22.13.9
'@types/geojson@7946.0.14': {}
@@ -10852,11 +10852,11 @@ snapshots:
'@types/jsonfile@6.1.4':
dependencies:
'@types/node': 22.13.8
'@types/node': 22.13.9
'@types/keyv@3.1.4':
dependencies:
'@types/node': 22.13.8
'@types/node': 22.13.9
'@types/lodash-es@4.17.12':
dependencies:
@@ -10876,7 +10876,7 @@ snapshots:
dependencies:
undici-types: 6.20.0
'@types/node@22.13.8':
'@types/node@22.13.9':
dependencies:
undici-types: 6.20.0
@@ -10906,7 +10906,7 @@ snapshots:
'@types/responselike@1.0.3':
dependencies:
'@types/node': 22.13.8
'@types/node': 22.13.9
'@types/retry@0.12.2': {}
@@ -10926,7 +10926,7 @@ snapshots:
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 22.13.8
'@types/node': 22.13.9
optional: true
'@typescript-eslint/eslint-plugin@8.25.0(@typescript-eslint/parser@8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)':
@@ -11045,7 +11045,7 @@ snapshots:
'@ungap/structured-clone@1.2.0': {}
'@vitejs/plugin-legacy@6.0.2(terser@5.36.0)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))':
'@vitejs/plugin-legacy@6.0.2(terser@5.36.0)(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))':
dependencies:
'@babel/core': 7.26.9
'@babel/preset-env': 7.26.9(@babel/core@7.26.9)
@@ -11056,25 +11056,25 @@ snapshots:
regenerator-runtime: 0.14.1
systemjs: 6.15.1
terser: 5.36.0
vite: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vite: 6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
transitivePeerDependencies:
- supports-color
'@vitejs/plugin-react-swc@3.8.0(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))':
'@vitejs/plugin-react-swc@3.8.0(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))':
dependencies:
'@swc/core': 1.10.15
vite: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vite: 6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
transitivePeerDependencies:
- '@swc/helpers'
'@vitejs/plugin-react@4.3.4(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))':
'@vitejs/plugin-react@4.3.4(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))':
dependencies:
'@babel/core': 7.26.0
'@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0)
'@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0)
'@types/babel__core': 7.20.5
react-refresh: 0.14.2
vite: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vite: 6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
transitivePeerDependencies:
- supports-color
@@ -11717,9 +11717,9 @@ snapshots:
core-js@3.41.0: {}
cosmiconfig-typescript-loader@6.1.0(@types/node@22.13.8)(cosmiconfig@9.0.0(typescript@5.8.2))(typescript@5.8.2):
cosmiconfig-typescript-loader@6.1.0(@types/node@22.13.9)(cosmiconfig@9.0.0(typescript@5.8.2))(typescript@5.8.2):
dependencies:
'@types/node': 22.13.8
'@types/node': 22.13.9
cosmiconfig: 9.0.0(typescript@5.8.2)
jiti: 2.4.2
typescript: 5.8.2
@@ -13699,11 +13699,11 @@ snapshots:
kind-of@6.0.3: {}
knip@5.45.0(@types/node@22.13.8)(typescript@5.8.2):
knip@5.45.0(@types/node@22.13.9)(typescript@5.8.2):
dependencies:
'@nodelib/fs.walk': 3.0.1
'@snyk/github-codeowners': 1.1.0
'@types/node': 22.13.8
'@types/node': 22.13.9
easy-table: 1.2.0
enhanced-resolve: 5.18.1
fast-glob: 3.3.3
@@ -16206,9 +16206,9 @@ snapshots:
- rollup
- supports-color
vite-plugin-dts@4.5.3(@types/node@22.13.8)(rollup@4.34.3)(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)):
vite-plugin-dts@4.5.3(@types/node@22.13.9)(rollup@4.34.3)(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)):
dependencies:
'@microsoft/api-extractor': 7.51.0(@types/node@22.13.8)
'@microsoft/api-extractor': 7.51.0(@types/node@22.13.9)
'@rollup/pluginutils': 5.1.4(rollup@4.34.3)
'@volar/typescript': 2.4.11
'@vue/language-core': 2.2.0(typescript@5.8.2)
@@ -16219,13 +16219,13 @@ snapshots:
magic-string: 0.30.17
typescript: 5.8.2
optionalDependencies:
vite: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vite: 6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
transitivePeerDependencies:
- '@types/node'
- rollup
- supports-color
vite-plugin-html@3.2.2(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)):
vite-plugin-html@3.2.2(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)):
dependencies:
'@rollup/pluginutils': 4.2.1
colorette: 2.0.20
@@ -16239,45 +16239,45 @@ snapshots:
html-minifier-terser: 6.1.0
node-html-parser: 5.4.2
pathe: 0.2.0
vite: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vite: 6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vite-plugin-sass-dts@1.3.30(postcss@8.5.3)(prettier@3.5.2)(sass-embedded@1.85.1)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)):
vite-plugin-sass-dts@1.3.30(postcss@8.5.3)(prettier@3.5.2)(sass-embedded@1.85.1)(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)):
dependencies:
postcss: 8.5.3
postcss-js: 4.0.1(postcss@8.5.3)
prettier: 3.5.2
sass-embedded: 1.85.1
vite: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vite: 6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vite-plugin-svgr@4.3.0(rollup@4.34.3)(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)):
vite-plugin-svgr@4.3.0(rollup@4.34.3)(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)):
dependencies:
'@rollup/pluginutils': 5.1.3(rollup@4.34.3)
'@svgr/core': 8.1.0(typescript@5.8.2)
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.2))
vite: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vite: 6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
transitivePeerDependencies:
- rollup
- supports-color
- typescript
vite-tsconfig-paths@5.1.4(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)):
vite-tsconfig-paths@5.1.4(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)):
dependencies:
debug: 4.3.7
globrex: 0.1.2
tsconfck: 3.0.3(typescript@5.8.2)
optionalDependencies:
vite: 6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
vite: 6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
transitivePeerDependencies:
- supports-color
- typescript
vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0):
vite@6.2.0(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0):
dependencies:
esbuild: 0.25.0
postcss: 8.5.3
rollup: 4.34.3
optionalDependencies:
'@types/node': 22.13.8
'@types/node': 22.13.9
fsevents: 2.3.3
jiti: 2.4.2
less: 4.2.0

View File

@@ -2,7 +2,7 @@ import * as path from 'jsr:@std/path'
import { globby } from 'npm:globby'
import { consola } from './utils/logger.ts'
const WORKSPACE_ROOT = path.join(Deno.cwd(), '../..')
const WORKSPACE_ROOT = path.join(import.meta.dirname!, '../..')
consola.info(`WORKSPACE_ROOT: ${WORKSPACE_ROOT}`)
const GITHUB_TOKEN = Deno.env.get('GITHUB_TOKEN') || Deno.env.get('GH_TOKEN')
@@ -29,8 +29,10 @@ for (let file of files) {
file = path.join(BACKEND_BUILD_DIR, file)
const p = path.parse(file)
consola.info(`Found file: ${p.base}`)
if (p.base.endsWith('.app.tar.gz')) {
const newName = p.name.split('.')[0] + `.${TARGET_ARCH}.app.tar.gz`
if (p.base.includes('.app')) {
const components = p.base.split('.')
const newName =
components[0] + `.${TARGET_ARCH}.${components.slice(1).join('.')}`
const newPath = path.join(p.dir, newName)
consola.info(`Renaming ${file} to ${newPath}`)
await Deno.rename(file, newPath)

View File

@@ -1078,6 +1078,7 @@ dependencies = [
"imageproc",
"log",
"log4rs",
"mihomo_api",
"mockito",
"nanoid",
"network-interface",
@@ -3723,9 +3724,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
[[package]]
name = "log"
version = "0.4.25"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
dependencies = [
"serde",
]
@@ -3896,6 +3897,16 @@ dependencies = [
"autocfg",
]
[[package]]
name = "mihomo_api"
version = "0.0.0"
dependencies = [
"reqwest",
"serde",
"serde_json",
"tokio",
]
[[package]]
name = "mime"
version = "0.3.17"
@@ -5996,9 +6007,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.217"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [
"serde_derive",
]
@@ -6038,9 +6049,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.217"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [
"proc-macro2",
"quote",
@@ -6060,9 +6071,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.138"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa 1.0.14",
"memchr",

View File

@@ -68,6 +68,7 @@ tokio-tungstenite = "0.26.1"
futures = "0.3"
sys-locale = "0.3.1"
async-trait = "0.1.86"
mihomo_api = { path = "./src/crate_mihomo_api" }
[target.'cfg(windows)'.dependencies]
runas = "=1.2.0"
@@ -126,3 +127,8 @@ crate-type = ["staticlib", "cdylib", "rlib"]
env_logger = "0.11.0"
mockito = "1.2.0"
tempfile = "3.17.1"
[workspace]
members = [
"src/crate_mihomo_api"
]

View File

@@ -1,9 +1,5 @@
use crate::{
utils::dirs,
feat,
wrap_err,
};
use super::CmdResult;
use crate::{feat, utils::dirs, wrap_err};
use tauri::Manager;
/// 打开应用程序所在目录
@@ -93,26 +89,47 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
Ok(icon_path.to_string_lossy().to_string())
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct IconInfo {
name: String,
previous_t: String,
current_t: String,
}
/// 复制图标文件
#[tauri::command]
pub fn copy_icon_file(path: String, name: String) -> CmdResult<String> {
let file_path = std::path::Path::new(&path);
pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
use std::fs;
use std::path::Path;
let file_path = Path::new(&path);
let icon_dir = wrap_err!(dirs::app_home_dir())?.join("icons");
if !icon_dir.exists() {
let _ = std::fs::create_dir_all(&icon_dir);
let _ = fs::create_dir_all(&icon_dir);
}
let ext = match file_path.extension() {
Some(e) => e.to_string_lossy().to_string(),
None => "ico".to_string(),
};
let png_dest_path = icon_dir.join(format!("{name}.png"));
let ico_dest_path = icon_dir.join(format!("{name}.ico"));
let dest_path = icon_dir.join(format!("{name}.{ext}"));
let dest_path = icon_dir.join(format!(
"{0}-{1}.{ext}",
icon_info.name, icon_info.current_t
));
if file_path.exists() {
std::fs::remove_file(png_dest_path).unwrap_or_default();
std::fs::remove_file(ico_dest_path).unwrap_or_default();
match std::fs::copy(file_path, &dest_path) {
if icon_info.previous_t.trim() != "" {
fs::remove_file(
icon_dir.join(format!("{0}-{1}.png", icon_info.name, icon_info.previous_t)),
)
.unwrap_or_default();
fs::remove_file(
icon_dir.join(format!("{0}-{1}.ico", icon_info.name, icon_info.previous_t)),
)
.unwrap_or_default();
}
match fs::copy(file_path, &dest_path) {
Ok(_) => Ok(dest_path.to_string_lossy().to_string()),
Err(err) => Err(err.to_string()),
}

View File

@@ -1,35 +1,26 @@
use super::CmdResult;
use crate::module::mihomo::MihomoManager;
use tauri::async_runtime;
use crate::core;
use mihomo_api;
#[tauri::command]
pub async fn get_proxies() -> CmdResult<serde_json::Value> {
let proxies = async_runtime::spawn_blocking(|| {
let rt = tokio::runtime::Runtime::new().unwrap();
let manager = MihomoManager::new();
{
let mut write_guard = manager.write();
rt.block_on(write_guard.refresh_proxies());
}
let read_guard = manager.read();
read_guard.fetch_proxies().clone()
})
.await.map_err(|e| e.to_string())?;
Ok(proxies)
let (mihomo_server, _) = core::clash_api::clash_client_info().unwrap();
let mihomo = mihomo_api::MihomoManager::new(mihomo_server);
Ok(mihomo
.refresh_proxies()
.await
.unwrap()
.get_proxies())
}
#[tauri::command]
pub async fn get_providers_proxies() -> CmdResult<serde_json::Value> {
let providers_proxies = async_runtime::spawn_blocking(|| {
let rt = tokio::runtime::Runtime::new().unwrap();
let manager = MihomoManager::new();
{
let mut write_guard = manager.write();
rt.block_on(write_guard.refresh_providers_proxies());
}
let read_guard = manager.read();
read_guard.fetch_providers_proxies().clone()
})
.await.map_err(|e| e.to_string())?;
Ok(providers_proxies)
}
let (mihomo_server, _) = core::clash_api::clash_client_info().unwrap();
let mihomo = mihomo_api::MihomoManager::new(mihomo_server);
Ok(mihomo
.refresh_providers_proxies()
.await
.unwrap()
.get_providers_proxies())
}

View File

@@ -1,5 +1,6 @@
use super::CmdResult;
use crate::{core::handle, model::sysinfo::PlatformSpecification};
use crate::core::handle;
use crate::module::sysinfo::PlatformSpecification;
use tauri_plugin_clipboard_manager::ClipboardExt;
use crate::{core::{self, CoreManager, service}, wrap_err};

View File

@@ -1 +0,0 @@
pub const MIHOMO_URL: &str = concat!("http://", "127.0.0.1", ":", "9097");

View File

@@ -1 +0,0 @@
pub mod mihomo;

View File

@@ -21,6 +21,3 @@ pub const DEFAULT_PAC: &str = r#"function FindProxyForURL(url, host) {
return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";
}
"#;
pub mod api;

View File

@@ -76,7 +76,7 @@ pub async fn get_proxy_delay(
}
/// 根据clash info获取clash服务地址和请求头
fn clash_client_info() -> Result<(String, HeaderMap)> {
pub fn clash_client_info() -> Result<(String, HeaderMap)> {
let client = { Config::clash().data().get_client_info() };
let server = format!("http://{}", client.server);

View File

@@ -0,0 +1,14 @@
[package]
name = "mihomo_api"
edition = "2024"
[features]
debug = []
[dependencies]
reqwest = { version = "0.12.12", features = ["json"] }
serde = { version = "1.0.218", features = ["derive"] }
serde_json = "1.0.140"
[dev-dependencies]
tokio = { version = "1.43.0", features = ["rt", "macros"] }

View File

@@ -0,0 +1,76 @@
use std::{
sync::{Arc, Mutex},
time::Duration,
};
pub mod model;
pub use model::{MihomoData, MihomoManager};
impl MihomoManager {
pub fn new(mihomo_server: String) -> Self {
Self {
mihomo_server,
data: Arc::new(Mutex::new(MihomoData {
proxies: serde_json::Value::Null,
providers_proxies: serde_json::Value::Null,
})),
}
}
fn update_proxies(&self, proxies: serde_json::Value) {
let mut data = self.data.lock().unwrap();
data.proxies = proxies;
}
fn update_providers_proxies(&self, providers_proxies: serde_json::Value) {
let mut data = self.data.lock().unwrap();
data.providers_proxies = providers_proxies;
}
pub fn get_proxies(&self) -> serde_json::Value {
let data = self.data.lock().unwrap();
data.proxies.clone()
}
pub fn get_providers_proxies(&self) -> serde_json::Value {
let data = self.data.lock().unwrap();
data.providers_proxies.clone()
}
pub async fn refresh_proxies(&self) -> Result<&Self, String> {
let url = format!("{}/proxies", self.mihomo_server);
let response = reqwest::ClientBuilder::new()
.no_proxy()
.timeout(Duration::from_secs(3))
.build()
.map_err(|e| e.to_string())?
.get(url)
.send()
.await
.map_err(|e| e.to_string())?
.json::<serde_json::Value>()
.await
.map_err(|e| e.to_string())?;
let proxies = response;
self.update_proxies(proxies);
Ok(self)
}
pub async fn refresh_providers_proxies(&self) -> Result<&Self, String> {
let url = format!("{}/providers/proxies", self.mihomo_server);
let response = reqwest::ClientBuilder::new()
.no_proxy()
.timeout(Duration::from_secs(3))
.build()
.map_err(|e| e.to_string())?
.get(url)
.send()
.await
.map_err(|e| e.to_string())?
.json::<serde_json::Value>()
.await
.map_err(|e| e.to_string())?;
let proxies = response;
self.update_providers_proxies(proxies);
Ok(self)
}
}

View File

@@ -0,0 +1,27 @@
use std::sync::{Arc, Mutex};
pub struct MihomoData {
pub(crate) proxies: serde_json::Value,
pub(crate) providers_proxies: serde_json::Value,
}
#[derive(Clone)]
pub struct MihomoManager {
pub(crate) mihomo_server: String,
pub(crate) data: Arc<Mutex<MihomoData>>,
}
#[cfg(feature = "debug")]
impl Drop for MihomoData {
fn drop(&mut self) {
println!("Dropping MihomoData");
}
}
#[cfg(feature = "debug")]
impl Drop for MihomoManager {
fn drop(&mut self) {
println!("Dropping MihomoManager");
}
}

View File

@@ -0,0 +1,28 @@
use mihomo_api;
#[test]
fn test_mihomo_manager_init() {
let manager = mihomo_api::MihomoManager::new("url".into());
assert_eq!(manager.get_proxies(), serde_json::Value::Null);
assert_eq!(manager.get_providers_proxies(), serde_json::Value::Null);
}
#[tokio::test]
async fn test_refresh_proxies() {
let manager = mihomo_api::MihomoManager::new("http://127.0.0.1:9097".into());
let manager = manager.refresh_proxies().await.unwrap();
let proxies = manager.get_proxies();
let providers = manager.get_providers_proxies();
assert_ne!(proxies, serde_json::Value::Null);
assert_eq!(providers, serde_json::Value::Null);
}
#[tokio::test]
async fn test_refresh_providers_proxies() {
let manager = mihomo_api::MihomoManager::new("http://127.0.0.1:9097".into());
let manager = manager.refresh_providers_proxies().await.unwrap();
let proxies = manager.get_proxies();
let providers = manager.get_providers_proxies();
assert_eq!(proxies, serde_json::Value::Null);
assert_ne!(providers, serde_json::Value::Null);
}

View File

@@ -4,7 +4,6 @@ mod core;
mod enhance;
mod feat;
mod utils;
mod model;
mod module;
use crate::core::hotkey;
use crate::utils::{resolve, resolve::resolve_scheme, server};

View File

@@ -1,20 +0,0 @@
use reqwest::Client;
#[allow(unused)]
pub(crate) struct ApiCaller<'a> {
pub(crate) url: &'a str,
pub(crate) client: Client,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_api_caller() {
let _api_caller = ApiCaller {
url: "https://example.com",
client: Client::new(),
};
}
}

View File

@@ -1,5 +0,0 @@
use super::common::ApiCaller;
pub struct MihomoAPICaller {
pub(crate) caller: ApiCaller<'static>,
}

View File

@@ -1,2 +0,0 @@
pub mod common;
pub mod mihomo;

View File

@@ -1,2 +0,0 @@
pub mod api;
pub mod sysinfo;

View File

@@ -1,18 +0,0 @@
use std::fmt::{self, Debug, Formatter};
pub struct PlatformSpecification {
pub system_name: String,
pub system_version: String,
pub system_kernel_version: String,
pub system_arch: String,
}
impl Debug for PlatformSpecification {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"System Name: {}\nSystem Version: {}\nSystem kernel Version: {}\nSystem Arch: {}",
self.system_name, self.system_version, self.system_kernel_version, self.system_arch
)
}
}

View File

@@ -1,70 +0,0 @@
use crate::model::api::common::ApiCaller;
use async_trait::async_trait;
use reqwest::{
header::{HeaderMap, HeaderName, HeaderValue},
RequestBuilder,
};
use serde::de::DeserializeOwned;
impl<'a> ApiCaller<'a> {
pub async fn send_request(
&self,
method: &str,
path: &str,
body: Option<&str>,
headers: Option<Vec<(&str, &str)>>,
) -> Result<String, String> {
let full_url = format!("{}{}", self.url, path); // 拼接完整 URL
let mut request: RequestBuilder = match method {
"GET" => self.client.get(&full_url),
"POST" => self
.client
.post(&full_url)
.body(body.unwrap_or("").to_string()),
"PUT" => self
.client
.put(&full_url)
.body(body.unwrap_or("").to_string()),
"DELETE" => self.client.delete(&full_url),
_ => return Err("Unsupported HTTP method".to_string()),
};
// 处理 headers
if let Some(hdrs) = headers {
let mut header_map = HeaderMap::new();
for (key, value) in hdrs {
if let (Ok(header_name), Ok(header_value)) = (
HeaderName::from_bytes(key.as_bytes()),
HeaderValue::from_str(value),
) {
header_map.insert(header_name, header_value);
}
}
request = request.headers(header_map);
}
let response = request.send().await.map_err(|e| e.to_string())?;
response.text().await.map_err(|e| e.to_string())
}
}
#[allow(unused)]
#[async_trait]
pub trait ApiCallerTrait: Send + Sync {
async fn call_api<T>(
&self,
method: &str,
path: &str,
body: Option<&str>,
headers: Option<Vec<(&str, &str)>>
) -> Result<T, String>
where
T: DeserializeOwned + Send + Sync;
fn parse_json_response<T>(json_str: &str) -> Result<T, String>
where
T: DeserializeOwned,
{
serde_json::from_str(json_str).map_err(|e| e.to_string())
}
}

View File

@@ -1,108 +0,0 @@
use super::common::ApiCallerTrait;
use crate::config::api::mihomo::MIHOMO_URL;
use crate::model::api::common::ApiCaller;
use crate::model::api::mihomo::MihomoAPICaller;
use async_trait::async_trait;
use once_cell::sync::OnceCell;
use parking_lot::RwLock;
use reqwest::Client;
use serde::de::DeserializeOwned;
use std::sync::Arc;
impl MihomoAPICaller {
#[allow(dead_code)]
pub fn new() -> Arc<RwLock<Self>> {
static INSTANCE: OnceCell<Arc<RwLock<MihomoAPICaller>>> = OnceCell::new();
INSTANCE
.get_or_init(|| {
let client = Client::new();
Arc::new(RwLock::new(MihomoAPICaller {
caller: ApiCaller {
url: MIHOMO_URL,
client,
},
}))
})
.clone()
}
}
#[async_trait]
impl ApiCallerTrait for MihomoAPICaller {
async fn call_api<T>(
&self,
method: &str,
path: &str,
body: Option<&str>,
headers: Option<Vec<(&str, &str)>>,
) -> Result<T, String>
where
T: DeserializeOwned + Send + Sync,
{
let response = self
.caller
.send_request(method, path, body, headers)
.await
.map_err(|e| e.to_string())?;
Self::parse_json_response::<T>(&response)
}
}
#[allow(unused)]
impl MihomoAPICaller {
pub async fn get_proxies() -> Result<serde_json::Value, String> {
Self::new()
.read()
.call_api("GET", "/proxies", None, None)
.await
}
pub async fn get_providers_proxies() -> Result<serde_json::Value, String> {
Self::new()
.read()
.call_api("GET", "/providers/proxies", None, None)
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_mihomo_api_singleton() {
let mihomo_api_caller1 = MihomoAPICaller::new();
let mihomo_api_caller2 = MihomoAPICaller::new();
assert!(Arc::ptr_eq(&mihomo_api_caller1, &mihomo_api_caller2));
}
#[tokio::test]
async fn test_mihomo_api_version() {
let mihomo_caller = MihomoAPICaller::new();
let response: Result<serde_json::Value, String> = mihomo_caller
.read()
.call_api("GET", "/version", None, None)
.await;
assert!(response.is_ok());
}
#[tokio::test]
async fn test_mihomo_get_proxies() {
let response = MihomoAPICaller::get_proxies().await;
assert!(response.is_ok());
if let Ok(proxies) = &response {
assert!(!proxies.get("proxies").is_none());
}
}
#[tokio::test]
async fn test_mihomo_get_providers_proxies() {
let response = MihomoAPICaller::get_providers_proxies().await;
println!("{:?}", response);
assert!(response.is_ok());
if let Ok(providers_proxies) = &response {
assert!(!providers_proxies.get("providers").is_none());
}
}
}

View File

@@ -1,2 +0,0 @@
pub mod common;
pub mod mihomo;

View File

@@ -1,158 +0,0 @@
use crate::model::api::mihomo::MihomoAPICaller;
use once_cell::sync::OnceCell;
use parking_lot::RwLock;
use std::sync::Arc;
#[allow(unused)]
pub struct MihomoManager {
proxies: serde_json::Value,
providers_proxies: serde_json::Value,
}
#[allow(unused)]
impl MihomoManager {
pub fn new() -> Arc<RwLock<Self>> {
static INSTANCE: OnceCell<Arc<RwLock<MihomoManager>>> = OnceCell::new();
INSTANCE
.get_or_init(|| {
Arc::new(RwLock::new(MihomoManager {
proxies: serde_json::Value::Null,
providers_proxies: serde_json::Value::Null,
}))
})
.clone()
}
pub fn fetch_proxies(&self) -> &serde_json::Value {
&self.proxies
}
pub fn fetch_providers_proxies(&self) -> &serde_json::Value {
&self.providers_proxies
}
pub async fn refresh_proxies(&mut self) {
match MihomoAPICaller::get_proxies().await {
Ok(proxies) => {
self.proxies = proxies;
}
Err(e) => {
log::error!("Failed to get proxies: {}", e);
}
}
}
pub async fn refresh_providers_proxies(&mut self) {
match MihomoAPICaller::get_providers_proxies().await {
Ok(providers_proxies) => {
self.providers_proxies = providers_proxies;
},
Err(e) => {
log::error!("Failed to get providers proxies: {}", e);
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_mihomo_manager_singleton() {
let manager1 = MihomoManager::new();
let manager2 = MihomoManager::new();
assert!(
Arc::ptr_eq(&manager1, &manager2),
"Should return same instance"
);
let manager = manager1.read();
assert!(manager.proxies.is_null());
assert!(manager.providers_proxies.is_null());
}
#[tokio::test]
async fn test_refresh_proxies() {
let manager = MihomoManager::new();
// Test initial state
{
let data = manager.read();
assert!(data.proxies.is_null());
}
// Test refresh
{
let mut data = manager.write();
data.refresh_proxies().await;
// Note: Since this depends on external API call,
// we can only verify that the refresh call completes
// without panicking. For more thorough testing,
// we would need to mock the API caller.
}
}
#[tokio::test]
async fn test_refresh_providers_proxies() {
let manager = MihomoManager::new();
// Test initial state
{
let data = manager.read();
assert!(data.providers_proxies.is_null());
}
// Test refresh
{
let mut data = manager.write();
data.refresh_providers_proxies().await;
// Note: Since this depends on external API call,
// we can only verify that the refresh call completes
// without panicking. For more thorough testing,
// we would need to mock the API caller.
}
}
#[tokio::test]
async fn test_fetch_proxies() {
let manager = MihomoManager::new();
// Test initial state
{
let data = manager.read();
let proxies = data.fetch_proxies();
assert!(proxies.is_null());
}
// Test after refresh
{
let mut data = manager.write();
data.refresh_proxies().await;
let _proxies = data.fetch_proxies();
// Can only verify the method returns without panicking
// Would need API mocking for more thorough testing
}
}
#[tokio::test]
async fn test_fetch_providers_proxies() {
let manager = MihomoManager::new();
// Test initial state
{
let data = manager.read();
let providers_proxies = data.fetch_providers_proxies();
assert!(providers_proxies.is_null());
}
// Test after refresh
{
let mut data = manager.write();
data.refresh_providers_proxies().await;
let _providers_proxies = data.fetch_providers_proxies();
// Can only verify the method returns without panicking
// Would need API mocking for more thorough testing
}
}
}

View File

@@ -1,3 +1 @@
pub mod api;
pub mod sysinfo;
pub mod mihomo;
pub mod sysinfo;

View File

@@ -1,7 +1,26 @@
use crate::model::sysinfo::PlatformSpecification;
use crate::core::{handle, CoreManager};
use std::fmt::{self, Debug, Formatter};
use sysinfo::System;
pub struct PlatformSpecification {
system_name: String,
system_version: String,
system_kernel_version: String,
system_arch: String,
verge_version: String,
running_mode: String,
}
impl Debug for PlatformSpecification {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"System Name: {}\nSystem Version: {}\nSystem kernel Version: {}\nSystem Arch: {}\nVerge Version: {}\nRunning Mode: {}",
self.system_name, self.system_version, self.system_kernel_version, self.system_arch, self.verge_version, self.running_mode
)
}
}
impl PlatformSpecification {
pub fn new() -> Self {
let system_name = System::name().unwrap_or("Null".into());
@@ -9,11 +28,29 @@ impl PlatformSpecification {
let system_kernel_version = System::kernel_version().unwrap_or("Null".into());
let system_arch = std::env::consts::ARCH.to_string();
let handler = handle::Handle::global().app_handle().unwrap();
let config = handler.config();
let verge_version = config.version.clone().unwrap_or("Null".into());
// Get running mode asynchronously
let running_mode = tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
match CoreManager::global().get_running_mode().await {
crate::core::RunningMode::Service => "Service".to_string(),
crate::core::RunningMode::Sidecar => "Sidecar".to_string(),
crate::core::RunningMode::NotRunning => "Not Running".to_string(),
}
})
});
Self {
system_name,
system_version,
system_kernel_version,
system_arch
system_arch,
verge_version,
running_mode,
}
}
}

View File

@@ -22,6 +22,18 @@ import getSystem from "@/utils/get-system";
const OS = getSystem();
const getIcons = async (icon_dir: string, name: string) => {
const updateTime = localStorage.getItem(`icon_${name}_update_time`) || "";
const icon_png = await join(icon_dir, `${name}-${updateTime}.png`);
const icon_ico = await join(icon_dir, `${name}-${updateTime}.ico`);
return {
icon_png,
icon_ico,
};
};
export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation();
const { verge, patchVerge, mutateVerge } = useVerge();
@@ -37,13 +49,20 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
async function initIconPath() {
const appDir = await getAppDir();
const icon_dir = await join(appDir, "icons");
const common_icon_png = await join(icon_dir, "common.png");
const common_icon_ico = await join(icon_dir, "common.ico");
const sysproxy_icon_png = await join(icon_dir, "sysproxy.png");
const sysproxy_icon_ico = await join(icon_dir, "sysproxy.ico");
const tun_icon_png = await join(icon_dir, "tun.png");
const tun_icon_ico = await join(icon_dir, "tun.ico");
const { icon_png: common_icon_png, icon_ico: common_icon_ico } =
await getIcons(icon_dir, "common");
const { icon_png: sysproxy_icon_png, icon_ico: sysproxy_icon_ico } =
await getIcons(icon_dir, "sysproxy");
const { icon_png: tun_icon_png, icon_ico: tun_icon_ico } = await getIcons(
icon_dir,
"tun",
);
if (await exists(common_icon_ico)) {
setCommonIcon(common_icon_ico);
} else {
@@ -212,11 +231,13 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
},
],
});
if (selected) {
await copyIconFile(`${selected}`, "common");
await initIconPath();
onChangeData({ common_tray_icon: true });
patchVerge({ common_tray_icon: true });
console.log();
}
}
}}

View File

@@ -54,10 +54,6 @@ const SettingSystem = ({ onError }: Props) => {
proxy_auto_config,
} = verge ?? {};
const isProxyEnabled = proxy_auto_config
? autoproxy?.enable
: sysproxy?.enable;
const onSwitchFormat = (_e: any, value: boolean) => value;
const onChangeData = (patch: Partial<IVergeConfig>) => {
mutateVerge({ ...verge, ...patch }, false);
@@ -149,7 +145,13 @@ const SettingSystem = ({ onError }: Props) => {
icon={SettingsRounded}
onClick={() => sysproxyRef.current?.open()}
/>
{isProxyEnabled ? (
{proxy_auto_config ? (
autoproxy?.enable ? (
<PlayArrowRounded sx={{ color: "success.main", mr: 1 }} />
) : (
<PauseRounded sx={{ color: "error.main", mr: 1 }} />
)
) : sysproxy?.enable ? (
<PlayArrowRounded sx={{ color: "success.main", mr: 1 }} />
) : (
<PauseRounded sx={{ color: "error.main", mr: 1 }} />

View File

@@ -166,9 +166,12 @@ const Layout = () => {
}),
// verge 配置更新监听
addListener("verge://refresh-verge-config", () =>
mutate("getVergeConfig"),
),
addListener("verge://refresh-verge-config", () => {
mutate("getVergeConfig");
// 添加对系统代理状态的刷新
mutate("getSystemProxy");
mutate("getAutotemProxy");
}),
// 通知消息监听
addListener("verge://notice-message", ({ payload }) =>

View File

@@ -199,7 +199,19 @@ export async function copyIconFile(
path: string,
name: "common" | "sysproxy" | "tun",
) {
return invoke<void>("copy_icon_file", { path, name });
const key = `icon_${name}_update_time`;
const previousTime = localStorage.getItem(key) || "";
const currentTime = String(Date.now());
localStorage.setItem(key, currentTime);
const iconInfo = {
name,
previous_t: previousTime,
current_t: currentTime,
};
return invoke<void>("copy_icon_file", { path, iconInfo });
}
export async function downloadIconCache(url: string, name: string) {

View File

@@ -1219,7 +1219,8 @@
};
sdhci: mmc@ffbf0000 {
compatible = "rockchip,rk3528-dwcmshc";
compatible = "rockchip,rk3528-dwcmshc",
"rockchip,rk3588-dwcmshc";
reg = <0x0 0xffbf0000 0x0 0x10000>;
interrupts = <GIC_SPI 136 IRQ_TYPE_LEVEL_HIGH>;
assigned-clocks = <&cru BCLK_EMMC>, <&cru TCLK_EMMC>, <&cru CCLK_SRC_EMMC>;

View File

@@ -28,12 +28,15 @@
#phy-cells = <1>;
--- a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
+++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
@@ -299,7 +299,7 @@ static int rockchip_combphy_parse_dt(str
@@ -299,7 +299,10 @@ static int rockchip_combphy_parse_dt(str
priv->ext_refclk = device_property_present(dev, "rockchip,ext-refclk");
- priv->phy_rst = devm_reset_control_array_get_exclusive(dev);
+ priv->phy_rst = devm_reset_control_get(dev, "phy");
- priv->phy_rst = devm_reset_control_get(dev, "phy");
+ priv->phy_rst = devm_reset_control_get_exclusive(dev, "phy");
+ /* fallback to old behaviour */
+ if (PTR_ERR(priv->phy_rst) == -ENOENT)
+ priv->phy_rst = devm_reset_control_array_get_exclusive(dev);
if (IS_ERR(priv->phy_rst))
return dev_err_probe(dev, PTR_ERR(priv->phy_rst), "failed to get phy reset\n");

View File

@@ -1,100 +1,3 @@
--- a/drivers/mmc/host/sdhci-of-dwcmshc.c
+++ b/drivers/mmc/host/sdhci-of-dwcmshc.c
@@ -295,19 +295,20 @@ static void dwcmshc_rk3568_set_clock(str
0x3 << 19; /* post-change delay */
sdhci_writel(host, extra, dwc_priv->vendor_specific_area1 + DWCMSHC_EMMC_ATCTRL);
- if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200 ||
- host->mmc->ios.timing == MMC_TIMING_MMC_HS400)
+ if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200)
txclk_tapnum = priv->txclk_tapnum;
- if ((priv->devtype == DWCMSHC_RK3588) && host->mmc->ios.timing == MMC_TIMING_MMC_HS400) {
+ if (host->mmc->ios.timing == MMC_TIMING_MMC_HS400) {
txclk_tapnum = DLL_TXCLK_TAPNUM_90_DEGREES;
- extra = DLL_CMDOUT_SRC_CLK_NEG |
- DLL_CMDOUT_EN_SRC_CLK_NEG |
- DWCMSHC_EMMC_DLL_DLYENA |
- DLL_CMDOUT_TAPNUM_90_DEGREES |
- DLL_CMDOUT_TAPNUM_FROM_SW;
- sdhci_writel(host, extra, DECMSHC_EMMC_DLL_CMDOUT);
+ if (priv->devtype != DWCMSHC_RK3568) {
+ extra = DLL_CMDOUT_SRC_CLK_NEG |
+ DLL_CMDOUT_EN_SRC_CLK_NEG |
+ DWCMSHC_EMMC_DLL_DLYENA |
+ DLL_CMDOUT_TAPNUM_90_DEGREES |
+ DLL_CMDOUT_TAPNUM_FROM_SW;
+ sdhci_writel(host, extra, DECMSHC_EMMC_DLL_CMDOUT);
+ }
}
extra = DWCMSHC_EMMC_DLL_DLYENA |
@@ -355,6 +356,15 @@ static const struct sdhci_ops sdhci_dwcm
.adma_write_desc = dwcmshc_adma_write_desc,
};
+static const struct sdhci_ops sdhci_dwcmshc_rk3528_ops = {
+ .set_clock = dwcmshc_rk3568_set_clock,
+ .set_bus_width = sdhci_set_bus_width,
+ .set_uhs_signaling = dwcmshc_set_uhs_signaling,
+ .get_max_clock = sdhci_pltfm_clk_get_max_clock,
+ .reset = rk35xx_sdhci_reset,
+ .adma_write_desc = dwcmshc_adma_write_desc,
+};
+
static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = {
.ops = &sdhci_dwcmshc_ops,
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
@@ -378,6 +388,14 @@ static const struct sdhci_pltfm_data sdh
SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
};
+static const struct sdhci_pltfm_data sdhci_dwcmshc_rk3528_pdata = {
+ .ops = &sdhci_dwcmshc_rk3528_ops,
+ .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
+ SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
+ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
+ SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
+};
+
static int dwcmshc_rk35xx_init(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv)
{
int err;
@@ -443,6 +461,10 @@ static const struct of_device_id sdhci_d
.data = &sdhci_dwcmshc_rk35xx_pdata,
},
{
+ .compatible = "rockchip,rk3528-dwcmshc",
+ .data = &sdhci_dwcmshc_rk3528_pdata,
+ },
+ {
.compatible = "snps,dwcmshc-sdhci",
.data = &sdhci_dwcmshc_pdata,
},
@@ -521,17 +543,18 @@ static int dwcmshc_probe(struct platform
host->mmc_host_ops.request = dwcmshc_request;
host->mmc_host_ops.hs400_enhanced_strobe = dwcmshc_hs400_enhanced_strobe;
- if (pltfm_data == &sdhci_dwcmshc_rk35xx_pdata) {
+ if ((pltfm_data == &sdhci_dwcmshc_rk35xx_pdata) ||
+ (pltfm_data == &sdhci_dwcmshc_rk3528_pdata)) {
rk_priv = devm_kzalloc(&pdev->dev, sizeof(struct rk35xx_priv), GFP_KERNEL);
if (!rk_priv) {
err = -ENOMEM;
goto err_clk;
}
- if (of_device_is_compatible(pdev->dev.of_node, "rockchip,rk3588-dwcmshc"))
- rk_priv->devtype = DWCMSHC_RK3588;
- else
+ if (of_device_is_compatible(pdev->dev.of_node, "rockchip,rk3568-dwcmshc"))
rk_priv->devtype = DWCMSHC_RK3568;
+ else
+ rk_priv->devtype = DWCMSHC_RK3588;
priv->priv = rk_priv;
--- a/drivers/pci/controller/dwc/Makefile
+++ b/drivers/pci/controller/dwc/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_PCIE_QCOM_EP) += pcie-qcom-

View File

@@ -1,3 +1,16 @@
From 7c24d9902e1dab659e020798f0d682e0cd650a88 Mon Sep 17 00:00:00 2001
From: Jianwei Zheng <jianwei.zheng@rock-chips.com>
Date: Sun, 9 Oct 2022 11:22:44 +0800
Subject: [PATCH] phy: rockchip: inno-usb2: add usb2 phy support for rk3528
This patch add usb2 phy support for rk3528.
Signed-off-by: Jianwei Zheng <jianwei.zheng@rock-chips.com>
Change-Id: Ia4a861bccd6a37db4e1ba42cede66a6b07947b5d
---
drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 51 ++++++++++++++++
1 file changed, 51 insertions(+)
--- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
+++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
@@ -1905,6 +1905,56 @@ static const struct rockchip_usb2phy_cfg
@@ -11,10 +24,10 @@
+ .clkout_ctl = { 0x041c, 7, 2, 0, 0x27 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x6004c, 15, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x60074, 3, 2, 0, 3 },
+ .bvalid_det_st = { 0x60078, 3, 2, 0, 3 },
+ .bvalid_det_clr = { 0x6007c, 3, 2, 0, 3 },
+ .phy_sus = { 0x6004c, 8, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x60074, 2, 2, 0, 1 },
+ .bvalid_det_st = { 0x60078, 2, 2, 0, 1 },
+ .bvalid_det_clr = { 0x6007c, 2, 2, 0, 1 },
+ .idfall_det_en = { 0x60074, 5, 5, 0, 1 },
+ .idfall_det_st = { 0x60078, 5, 5, 0, 1 },
+ .idfall_det_clr = { 0x6007c, 5, 5, 0, 1 },
@@ -30,7 +43,7 @@
+ .utmi_ls = { 0x6006c, 5, 4, 0, 1 },
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x6005c, 15, 0, 0x1d2, 0x1d1 },
+ .phy_sus = { 0x6005c, 8, 0, 0x1d2, 0x1d1 },
+ .ls_det_en = { 0x60090, 0, 0, 0, 1 },
+ .ls_det_st = { 0x60094, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x60098, 0, 0, 0, 1 },

View File

@@ -1,96 +1,3 @@
--- a/drivers/mmc/host/sdhci-of-dwcmshc.c
+++ b/drivers/mmc/host/sdhci-of-dwcmshc.c
@@ -688,19 +688,20 @@ static void dwcmshc_rk3568_set_clock(str
0x3 << 19; /* post-change delay */
sdhci_writel(host, extra, dwc_priv->vendor_specific_area1 + DWCMSHC_EMMC_ATCTRL);
- if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200 ||
- host->mmc->ios.timing == MMC_TIMING_MMC_HS400)
+ if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200)
txclk_tapnum = priv->txclk_tapnum;
- if ((priv->devtype == DWCMSHC_RK3588) && host->mmc->ios.timing == MMC_TIMING_MMC_HS400) {
+ if (host->mmc->ios.timing == MMC_TIMING_MMC_HS400) {
txclk_tapnum = DLL_TXCLK_TAPNUM_90_DEGREES;
- extra = DLL_CMDOUT_SRC_CLK_NEG |
- DLL_CMDOUT_EN_SRC_CLK_NEG |
- DWCMSHC_EMMC_DLL_DLYENA |
- DLL_CMDOUT_TAPNUM_90_DEGREES |
- DLL_CMDOUT_TAPNUM_FROM_SW;
- sdhci_writel(host, extra, DECMSHC_EMMC_DLL_CMDOUT);
+ if (priv->devtype != DWCMSHC_RK3568) {
+ extra = DLL_CMDOUT_SRC_CLK_NEG |
+ DLL_CMDOUT_EN_SRC_CLK_NEG |
+ DWCMSHC_EMMC_DLL_DLYENA |
+ DLL_CMDOUT_TAPNUM_90_DEGREES |
+ DLL_CMDOUT_TAPNUM_FROM_SW;
+ sdhci_writel(host, extra, DECMSHC_EMMC_DLL_CMDOUT);
+ }
}
extra = DWCMSHC_EMMC_DLL_DLYENA |
@@ -741,10 +742,10 @@ static int dwcmshc_rk35xx_init(struct de
if (!priv)
return -ENOMEM;
- if (of_device_is_compatible(dev->of_node, "rockchip,rk3588-dwcmshc"))
- priv->devtype = DWCMSHC_RK3588;
- else
+ if (of_device_is_compatible(dev->of_node, "rockchip,rk3568-dwcmshc"))
priv->devtype = DWCMSHC_RK3568;
+ else
+ priv->devtype = DWCMSHC_RK3588;
priv->reset = devm_reset_control_array_get_optional_exclusive(mmc_dev(host->mmc));
if (IS_ERR(priv->reset)) {
@@ -1156,6 +1157,16 @@ static const struct sdhci_ops sdhci_dwcm
.irq = dwcmshc_cqe_irq_handler,
};
+static const struct sdhci_ops sdhci_dwcmshc_rk3528_ops = {
+ .set_clock = dwcmshc_rk3568_set_clock,
+ .set_bus_width = sdhci_set_bus_width,
+ .set_uhs_signaling = dwcmshc_set_uhs_signaling,
+ .get_max_clock = sdhci_pltfm_clk_get_max_clock,
+ .reset = rk35xx_sdhci_reset,
+ .adma_write_desc = dwcmshc_adma_write_desc,
+ .irq = dwcmshc_cqe_irq_handler,
+};
+
static const struct sdhci_ops sdhci_dwcmshc_th1520_ops = {
.set_clock = sdhci_set_clock,
.set_bus_width = sdhci_set_bus_width,
@@ -1218,6 +1229,18 @@ static const struct dwcmshc_pltfm_data s
.postinit = dwcmshc_rk35xx_postinit,
};
+static const struct dwcmshc_pltfm_data sdhci_dwcmshc_rk3528_pdata = {
+ .pdata = {
+ .ops = &sdhci_dwcmshc_rk3528_ops,
+ .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
+ SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
+ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
+ SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
+ },
+ .init = dwcmshc_rk35xx_init,
+ .postinit = dwcmshc_rk35xx_postinit,
+};
+
static const struct dwcmshc_pltfm_data sdhci_dwcmshc_th1520_pdata = {
.pdata = {
.ops = &sdhci_dwcmshc_th1520_ops,
@@ -1320,6 +1343,10 @@ static const struct of_device_id sdhci_d
.compatible = "rockchip,rk3568-dwcmshc",
.data = &sdhci_dwcmshc_rk35xx_pdata,
},
+ {
+ .compatible = "rockchip,rk3528-dwcmshc",
+ .data = &sdhci_dwcmshc_rk3528_pdata,
+ },
{
.compatible = "snps,dwcmshc-sdhci",
.data = &sdhci_dwcmshc_pdata,
--- a/drivers/pci/controller/dwc/Makefile
+++ b/drivers/pci/controller/dwc/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_PCIE_QCOM_EP) += pcie-qcom-

View File

@@ -1,100 +1,3 @@
--- a/drivers/mmc/host/sdhci-of-dwcmshc.c
+++ b/drivers/mmc/host/sdhci-of-dwcmshc.c
@@ -296,19 +296,20 @@ static void dwcmshc_rk3568_set_clock(str
0x3 << 19; /* post-change delay */
sdhci_writel(host, extra, dwc_priv->vendor_specific_area1 + DWCMSHC_EMMC_ATCTRL);
- if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200 ||
- host->mmc->ios.timing == MMC_TIMING_MMC_HS400)
+ if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200)
txclk_tapnum = priv->txclk_tapnum;
- if ((priv->devtype == DWCMSHC_RK3588) && host->mmc->ios.timing == MMC_TIMING_MMC_HS400) {
+ if (host->mmc->ios.timing == MMC_TIMING_MMC_HS400) {
txclk_tapnum = DLL_TXCLK_TAPNUM_90_DEGREES;
- extra = DLL_CMDOUT_SRC_CLK_NEG |
- DLL_CMDOUT_EN_SRC_CLK_NEG |
- DWCMSHC_EMMC_DLL_DLYENA |
- DLL_CMDOUT_TAPNUM_90_DEGREES |
- DLL_CMDOUT_TAPNUM_FROM_SW;
- sdhci_writel(host, extra, DECMSHC_EMMC_DLL_CMDOUT);
+ if (priv->devtype != DWCMSHC_RK3568) {
+ extra = DLL_CMDOUT_SRC_CLK_NEG |
+ DLL_CMDOUT_EN_SRC_CLK_NEG |
+ DWCMSHC_EMMC_DLL_DLYENA |
+ DLL_CMDOUT_TAPNUM_90_DEGREES |
+ DLL_CMDOUT_TAPNUM_FROM_SW;
+ sdhci_writel(host, extra, DECMSHC_EMMC_DLL_CMDOUT);
+ }
}
extra = DWCMSHC_EMMC_DLL_DLYENA |
@@ -356,6 +357,15 @@ static const struct sdhci_ops sdhci_dwcm
.adma_write_desc = dwcmshc_adma_write_desc,
};
+static const struct sdhci_ops sdhci_dwcmshc_rk3528_ops = {
+ .set_clock = dwcmshc_rk3568_set_clock,
+ .set_bus_width = sdhci_set_bus_width,
+ .set_uhs_signaling = dwcmshc_set_uhs_signaling,
+ .get_max_clock = sdhci_pltfm_clk_get_max_clock,
+ .reset = rk35xx_sdhci_reset,
+ .adma_write_desc = dwcmshc_adma_write_desc,
+};
+
static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = {
.ops = &sdhci_dwcmshc_ops,
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
@@ -379,6 +389,14 @@ static const struct sdhci_pltfm_data sdh
SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
};
+static const struct sdhci_pltfm_data sdhci_dwcmshc_rk3528_pdata = {
+ .ops = &sdhci_dwcmshc_rk3528_ops,
+ .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
+ SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
+ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
+ SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
+};
+
static int dwcmshc_rk35xx_init(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv)
{
int err;
@@ -444,6 +462,10 @@ static const struct of_device_id sdhci_d
.data = &sdhci_dwcmshc_rk35xx_pdata,
},
{
+ .compatible = "rockchip,rk3528-dwcmshc",
+ .data = &sdhci_dwcmshc_rk3528_pdata,
+ },
+ {
.compatible = "snps,dwcmshc-sdhci",
.data = &sdhci_dwcmshc_pdata,
},
@@ -523,17 +545,18 @@ static int dwcmshc_probe(struct platform
host->mmc_host_ops.request = dwcmshc_request;
host->mmc_host_ops.hs400_enhanced_strobe = dwcmshc_hs400_enhanced_strobe;
- if (pltfm_data == &sdhci_dwcmshc_rk35xx_pdata) {
+ if ((pltfm_data == &sdhci_dwcmshc_rk35xx_pdata) ||
+ (pltfm_data == &sdhci_dwcmshc_rk3528_pdata)) {
rk_priv = devm_kzalloc(&pdev->dev, sizeof(struct rk35xx_priv), GFP_KERNEL);
if (!rk_priv) {
err = -ENOMEM;
goto err_clk;
}
- if (of_device_is_compatible(pdev->dev.of_node, "rockchip,rk3588-dwcmshc"))
- rk_priv->devtype = DWCMSHC_RK3588;
- else
+ if (of_device_is_compatible(pdev->dev.of_node, "rockchip,rk3568-dwcmshc"))
rk_priv->devtype = DWCMSHC_RK3568;
+ else
+ rk_priv->devtype = DWCMSHC_RK3588;
priv->priv = rk_priv;
--- a/drivers/pci/controller/dwc/Makefile
+++ b/drivers/pci/controller/dwc/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_PCIE_QCOM_EP) += pcie-qcom-

View File

@@ -25,6 +25,7 @@ import (
)
func main() {
appctl.RecordAppStartTime()
appctl.SetAppType(appctl.CLIENT_APP)
debug.SetGCPercent(90)
cli.RegisterClientCommands()

View File

@@ -25,6 +25,7 @@ import (
)
func main() {
appctl.RecordAppStartTime()
appctl.SetAppType(appctl.SERVER_APP)
debug.SetGCPercent(90)
cli.RegisterServerCommands()

View File

@@ -0,0 +1,43 @@
// Copyright (C) 2025 mieru authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package appctl
import (
"sync"
"time"
)
var (
appStartTime time.Time
appStartTimeOnce sync.Once
)
// RecordAppStartTime record the app start time.
// This function does nothing after the first use.
func RecordAppStartTime() {
appStartTimeOnce.Do(func() {
appStartTime = time.Now()
})
}
// Elapsed returns the how long the app has been started.
// It panics if the start time is not recorded.
func Elapsed() time.Duration {
if appStartTime.IsZero() {
panic("app start time is not recorded")
}
return time.Since(appStartTime).Truncate(time.Microsecond)
}

View File

@@ -682,6 +682,7 @@ var clientRunFunc = func(s []string) error {
metrics.EnableLogging()
appctl.SetAppStatus(appctlpb.AppStatus_RUNNING)
log.Debugf("Started proxy after %v", appctl.Elapsed())
wg.Wait()
// Stop CPU profiling, if previously started.

View File

@@ -474,8 +474,10 @@ var serverRunFunc = func(s []string) error {
initProxyTasks.Wait()
metrics.EnableLogging()
appctl.SetAppStatus(appctlpb.AppStatus_RUNNING)
log.Debugf("Started proxy after %v", appctl.Elapsed())
proxyTasks.Wait()
}
// If fails to validate server configuration, do nothing.
// Most likely the server configuration is empty.
// It will be set by new RPC calls.

View File

@@ -293,7 +293,6 @@ jobs:
echo "CONFIG_PACKAGE_luci-app-passwall=m" >> .config
echo "CONFIG_PACKAGE_luci-app-passwall_Iptables_Transparent_Proxy=y" >> .config
echo "CONFIG_PACKAGE_luci-app-passwall_Nftables_Transparent_Proxy=y" >> .config
echo "CONFIG_PACKAGE_luci-app-passwall_INCLUDE_Geoview=y" >> .config
echo "CONFIG_PACKAGE_luci-app-passwall_INCLUDE_Haproxy=y" >> .config
echo "CONFIG_PACKAGE_luci-app-passwall_INCLUDE_Hysteria=y" >> .config
echo "CONFIG_PACKAGE_luci-app-passwall_INCLUDE_NaiveProxy=y" >> .config

View File

@@ -45,7 +45,34 @@ o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/chi
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/apple-cn.txt", translate("Loyalsoldier/apple-cn"))
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/google-cn.txt", translate("Loyalsoldier/google-cn"))
s:append(Template(appname .. "/rule/rule_version"))
if has_xray or has_singbox then
o = s:option(ListValue, "geoip_url", translate("GeoIP Update URL"))
o:value("https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest", translate("Loyalsoldier/geoip"))
o:value("https://api.github.com/repos/MetaCubeX/meta-rules-dat/releases/latest", translate("MetaCubeX/geoip"))
o.default = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
o = s:option(ListValue, "geosite_url", translate("Geosite Update URL"))
o:value("https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest", translate("Loyalsoldier/geosite"))
o:value("https://api.github.com/repos/MetaCubeX/meta-rules-dat/releases/latest", translate("MetaCubeX/geosite"))
o.default = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
o = s:option(Value, "v2ray_location_asset", translate("Location of Geo rule files"), translate("This variable specifies a directory where geoip.dat and geosite.dat files are."))
o.default = "/usr/share/v2ray/"
o.placeholder = "/usr/share/v2ray/"
o.rmempty = false
if api.is_finded("geoview") then
o = s:option(Flag, "enable_geoview", translate("Enable Geo Data Parsing"))
o.default = 0
o.rmempty = false
o.description = "<ul>"
.. "<li>" .. translate("Experimental feature.") .. "</li>"
.. "<li>" .. "1." .. translate("Analyzes and preloads GeoIP/Geosite data to enhance the shunt performance of Sing-box/Xray.") .. "</li>"
.. "<li>" .. "2." .. translate("Once enabled, the rule list can support GeoIP/Geosite rules.") .. "</li>"
.. "<li>" .. translate("Note: Increases resource usage; Geosite analysis is only supported in ChinaDNS-NG and SmartDNS modes.") .. "</li>"
.. "</ul>"
end
end
---- Auto Update
o = s:option(Flag, "auto_update", translate("Enable auto update rules"))
@@ -88,23 +115,9 @@ o.default = 2
o:depends("week_update", "8")
o.rmempty = true
s:append(Template(appname .. "/rule/rule_version"))
if has_xray or has_singbox then
o = s:option(Value, "v2ray_location_asset", translate("Location of V2ray/Xray asset"), translate("This variable specifies a directory where geoip.dat and geosite.dat files are."))
o.default = "/usr/share/v2ray/"
o.rmempty = false
if api.is_finded("geoview") then
o = s:option(Flag, "enable_geoview", translate("Enable Geo Data Parsing"))
o.default = 0
o.rmempty = false
o.description = "<ul>"
.. "<li>" .. translate("Experimental feature.") .. "</li>"
.. "<li>" .. "1." .. translate("Analyzes and preloads GeoIP/Geosite data to enhance the shunt performance of Sing-box/Xray.") .. "</li>"
.. "<li>" .. "2." .. translate("Once enabled, the rule list can support GeoIP/Geosite rules.") .. "</li>"
.. "<li>" .. translate("Note: Increases resource usage; Geosite analysis is only supported in ChinaDNS-NG and SmartDNS modes.") .. "</li>"
.. "</ul>"
end
s = m:section(TypedSection, "shunt_rules", "Sing-Box/Xray " .. translate("Shunt Rule"), "<a style='color: red'>" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "</a>")
s.template = "cbi/tblsection"
s.anonymous = false

View File

@@ -937,8 +937,14 @@ msgstr "小时"
msgid "Hour"
msgstr "小时"
msgid "Location of V2ray/Xray asset"
msgstr "V2ray/Xray 资源文件目录"
msgid "GeoIP Update URL"
msgstr "GeoIP 更新URL"
msgid "Geosite Update URL"
msgstr "Geosite 更新URL"
msgid "Location of Geo rule files"
msgstr "Geo 规则文件目录"
msgid "This variable specifies a directory where geoip.dat and geosite.dat files are."
msgstr "此变量指定 geoip.dat 和 geosite.dat 文件所在的目录。"

View File

@@ -72,6 +72,8 @@ config global_rules
list chnlist_url 'https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf'
list chnlist_url 'https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf'
option v2ray_location_asset '/usr/share/v2ray/'
option geoip_url 'https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest'
option geosite_url 'https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest'
config global_app
option singbox_file '/usr/bin/sing-box'

View File

@@ -350,8 +350,7 @@ parse_doh() {
get_geoip() {
local geoip_code="$1"
local geoip_type_flag=""
local geoip_path="$(config_t_get global_rules v2ray_location_asset)"
geoip_path="${geoip_path%*/}/geoip.dat"
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
[ -s "$geoip_path" ] || { echo ""; return 1; }
case "$2" in
"ipv4") geoip_type_flag="-ipv6=false" ;;
@@ -777,9 +776,8 @@ run_redir() {
sing-box)
local protocol=$(config_n_get $node protocol)
[ "$protocol" = "_shunt" ] && {
local geo_path="$(config_t_get global_rules v2ray_location_asset)"
local geoip_path="${geo_path%*/}/geoip.dat"
local geosite_path="${geo_path%*/}/geosite.dat"
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
local geosite_path="${V2RAY_LOCATION_ASSET%*/}/geosite.dat"
if [ ! -s "$geoip_path" ] || [ ! -s "$geosite_path" ]; then
echolog "* 缺少Geo规则文件UDP Sing-Box分流节点无法正常使用"
fi
@@ -789,9 +787,8 @@ run_redir() {
xray)
local protocol=$(config_n_get $node protocol)
[ "$protocol" = "_shunt" ] && {
local geo_path="$(config_t_get global_rules v2ray_location_asset)"
local geoip_path="${geo_path%*/}/geoip.dat"
local geosite_path="${geo_path%*/}/geosite.dat"
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
local geosite_path="${V2RAY_LOCATION_ASSET%*/}/geosite.dat"
if [ ! -s "$geoip_path" ] || [ ! -s "$geosite_path" ]; then
echolog "* 缺少Geo规则文件UDP Xray分流节点无法正常使用"
fi
@@ -896,9 +893,8 @@ run_redir() {
}
[ "$protocol" = "_shunt" ] && {
local geo_path="$(config_t_get global_rules v2ray_location_asset)"
local geoip_path="${geo_path%*/}/geoip.dat"
local geosite_path="${geo_path%*/}/geosite.dat"
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
local geosite_path="${V2RAY_LOCATION_ASSET%*/}/geosite.dat"
if [ ! -s "$geoip_path" ] || [ ! -s "$geosite_path" ]; then
echolog "* 缺少Geo规则文件TCP Sing-Box分流节点无法正常使用"
fi
@@ -983,9 +979,8 @@ run_redir() {
}
[ "$protocol" = "_shunt" ] && {
local geo_path="$(config_t_get global_rules v2ray_location_asset)"
local geoip_path="${geo_path%*/}/geoip.dat"
local geosite_path="${geo_path%*/}/geosite.dat"
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
local geosite_path="${V2RAY_LOCATION_ASSET%*/}/geosite.dat"
if [ ! -s "$geoip_path" ] || [ ! -s "$geosite_path" ]; then
echolog "* 缺少Geo规则文件TCP Xray分流节点无法正常使用"
fi

View File

@@ -88,7 +88,7 @@ local function insert_array_after(array1, array2, target) --将array2插入到ar
end
local function get_geosite(list_arg, out_path)
local geosite_path = uci:get(appname, "@global_rules[0]", "v2ray_location_asset")
local geosite_path = uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"
geosite_path = geosite_path:match("^(.*)/") .. "/geosite.dat"
if not is_file_nonzero(geosite_path) then return 1 end
if api.is_finded("geoview") and list_arg and out_path then

View File

@@ -92,7 +92,7 @@ local function insert_array_after(array1, array2, target) --将array2插入到ar
end
local function get_geosite(list_arg, out_path)
local geosite_path = uci:get(appname, "@global_rules[0]", "v2ray_location_asset")
local geosite_path = uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"
geosite_path = geosite_path:match("^(.*)/") .. "/geosite.dat"
if not is_file_nonzero(geosite_path) then return 1 end
if api.is_finded("geoview") and list_arg and out_path then

View File

@@ -33,8 +33,8 @@ local gfwlist_url = uci:get(name, "@global_rules[0]", "gfwlist_url") or {"https:
local chnroute_url = uci:get(name, "@global_rules[0]", "chnroute_url") or {"https://ispip.clang.cn/all_cn.txt"}
local chnroute6_url = uci:get(name, "@global_rules[0]", "chnroute6_url") or {"https://ispip.clang.cn/all_cn_ipv6.txt"}
local chnlist_url = uci:get(name, "@global_rules[0]", "chnlist_url") or {"https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf","https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf","https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/google.china.conf"}
local geoip_api = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
local geosite_api = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
local geoip_api = uci:get(name, "@global_rules[0]", "geoip_url") or "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
local geosite_api = uci:get(name, "@global_rules[0]", "geosite_url") or "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
local asset_location = uci:get(name, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"
local use_nft = uci:get(name, "@global_forwarding[0]", "use_nft") or "0"

View File

@@ -1471,9 +1471,9 @@ dependencies = [
[[package]]
name = "httparse"
version = "1.10.0"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "httpdate"
@@ -2412,18 +2412,18 @@ dependencies = [
[[package]]
name = "pin-project"
version = "1.1.9"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.9"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -1,6 +1,6 @@
include $(TOPDIR)/rules.mk
PKG_VERSION:=1.20.0
PKG_VERSION:=1.20.1
LUCI_TITLE:=LuCI Support for nikki
LUCI_DEPENDS:=+luci-base +nikki

View File

@@ -114,7 +114,7 @@ return baseclass.extend({
openDashboard: async function () {
const uiName = uci.get('nikki', 'mixin', 'ui_name');
const apiListen = uci.get('nikki', 'mixin', 'api_listen');
const apiSecret = encodeURIComponent(uci.get('nikki', 'mixin', 'api_secret') ?? '');
const apiSecret = uci.get('nikki', 'mixin', 'api_secret') ?? '';
const apiPort = apiListen.substring(apiListen.lastIndexOf(':') + 1);
const params = {
host: window.location.hostname,

View File

@@ -12,7 +12,6 @@ PKG_RELEASE:=1
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
CONFIG_PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Geoview \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Haproxy \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy \
@@ -36,7 +35,7 @@ LUCI_PKGARCH:=all
LUCI_DEPENDS:=+coreutils +coreutils-base64 +coreutils-nohup +curl \
+chinadns-ng +dns2socks +dnsmasq-full +ip-full \
+libuci-lua +lua +luci-compat +luci-lib-jsonc \
+microsocks +resolveip +tcping
+microsocks +resolveip +tcping +geoview
define Package/$(PKG_NAME)/config
menu "Configuration"
@@ -64,11 +63,6 @@ config PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy
select PACKAGE_kmod-nft-nat
default y if PACKAGE_firewall4
config PACKAGE_$(PKG_NAME)_INCLUDE_Geoview
bool "Include Geoview"
select PACKAGE_geoview
default y if aarch64||arm||i386||x86_64
config PACKAGE_$(PKG_NAME)_INCLUDE_Haproxy
bool "Include Haproxy"
select PACKAGE_haproxy

View File

@@ -242,42 +242,6 @@ if has_singbox then
o.default = 0
o.rmempty = false
o.description = translate("Override the connection destination address with the sniffed domain.<br />When enabled, traffic will match only by domain, ignoring IP rules.<br />If using shunt nodes, configure the domain shunt rules correctly.")
o = s:option(Value, "geoip_path", translate("Custom geoip Path"))
o.default = "/usr/share/singbox/geoip.db"
o.rmempty = false
o = s:option(Value, "geoip_url", translate("Custom geoip URL"))
o.default = "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.db"
o:value("https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.db")
o:value("https://github.com/1715173329/sing-geoip/releases/latest/download/geoip.db")
o:value("https://github.com/lyc8503/sing-box-rules/releases/latest/download/geoip.db")
o.rmempty = false
o = s:option(Value, "geosite_path", translate("Custom geosite Path"))
o.default = "/usr/share/singbox/geosite.db"
o.rmempty = false
o = s:option(Value, "geosite_url", translate("Custom geosite URL"))
o.default = "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.db"
o:value("https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.db")
o:value("https://github.com/1715173329/sing-geosite/releases/latest/download/geosite.db")
o:value("https://github.com/lyc8503/sing-box-rules/releases/latest/download/geosite.db")
o.rmempty = false
o = s:option(Button, "_remove_resource", translate("Remove resource files"))
o.description = translate("Sing-Box will automatically download resource files when starting, you can use this feature achieve upgrade resource files.")
o.inputstyle = "remove"
function o.write(self, section, value)
local geoip_path = s.fields["geoip_path"] and s.fields["geoip_path"]:formvalue(section) or nil
if geoip_path then
os.remove(geoip_path)
end
local geosite_path = s.fields["geosite_path"] and s.fields["geosite_path"]:formvalue(section) or nil
if geosite_path then
os.remove(geosite_path)
end
end
end
return m

View File

@@ -45,7 +45,34 @@ o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/chi
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/apple-cn.txt", translate("Loyalsoldier/apple-cn"))
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/google-cn.txt", translate("Loyalsoldier/google-cn"))
s:append(Template(appname .. "/rule/rule_version"))
if has_xray or has_singbox then
o = s:option(ListValue, "geoip_url", translate("GeoIP Update URL"))
o:value("https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest", translate("Loyalsoldier/geoip"))
o:value("https://api.github.com/repos/MetaCubeX/meta-rules-dat/releases/latest", translate("MetaCubeX/geoip"))
o.default = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
o = s:option(ListValue, "geosite_url", translate("Geosite Update URL"))
o:value("https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest", translate("Loyalsoldier/geosite"))
o:value("https://api.github.com/repos/MetaCubeX/meta-rules-dat/releases/latest", translate("MetaCubeX/geosite"))
o.default = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
o = s:option(Value, "v2ray_location_asset", translate("Location of Geo rule files"), translate("This variable specifies a directory where geoip.dat and geosite.dat files are."))
o.default = "/usr/share/v2ray/"
o.placeholder = "/usr/share/v2ray/"
o.rmempty = false
if api.is_finded("geoview") then
o = s:option(Flag, "enable_geoview", translate("Enable Geo Data Parsing"))
o.default = 0
o.rmempty = false
o.description = "<ul>"
.. "<li>" .. translate("Experimental feature.") .. "</li>"
.. "<li>" .. "1." .. translate("Analyzes and preloads GeoIP/Geosite data to enhance the shunt performance of Sing-box/Xray.") .. "</li>"
.. "<li>" .. "2." .. translate("Once enabled, the rule list can support GeoIP/Geosite rules.") .. "</li>"
.. "<li>" .. translate("Note: Increases resource usage; Geosite analysis is only supported in ChinaDNS-NG and SmartDNS modes.") .. "</li>"
.. "</ul>"
end
end
---- Auto Update
o = s:option(Flag, "auto_update", translate("Enable auto update rules"))
@@ -88,23 +115,9 @@ o.default = 2
o:depends("week_update", "8")
o.rmempty = true
s:append(Template(appname .. "/rule/rule_version"))
if has_xray or has_singbox then
o = s:option(Value, "v2ray_location_asset", translate("Location of V2ray/Xray asset"), translate("This variable specifies a directory where geoip.dat and geosite.dat files are."))
o.default = "/usr/share/v2ray/"
o.rmempty = false
if api.is_finded("geoview") then
o = s:option(Flag, "enable_geoview", translate("Enable Geo Data Parsing"))
o.default = 0
o.rmempty = false
o.description = "<ul>"
.. "<li>" .. translate("Experimental feature.") .. "</li>"
.. "<li>" .. "1." .. translate("Analyzes and preloads GeoIP/Geosite data to enhance the shunt performance of Sing-box/Xray.") .. "</li>"
.. "<li>" .. "2." .. translate("Once enabled, the rule list can support GeoIP/Geosite rules.") .. "</li>"
.. "<li>" .. translate("Note: Increases resource usage; Geosite analysis is only supported in ChinaDNS-NG and SmartDNS modes.") .. "</li>"
.. "</ul>"
end
s = m:section(TypedSection, "shunt_rules", "Sing-Box/Xray " .. translate("Shunt Rule"), "<a style='color: red'>" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "</a>")
s.template = "cbi/tblsection"
s.anonymous = false

View File

@@ -10,6 +10,41 @@ local split = api.split
local local_version = api.get_app_version("singbox")
local version_ge_1_11_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.11.0")
local geosite_all_tag = {}
local geoip_all_tag = {}
local srss_path = "/tmp/etc/" .. appname .."/srss/"
local function convert_geofile()
local geo_path = uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"
local geosite_path = geo_path:match("^(.*)/") .. "/geosite.dat"
local geoip_path = geo_path:match("^(.*)/") .. "/geoip.dat"
if not api.is_finded("geoview") then
api.log("* 注意:缺少 geoview 组件Sing-Box 分流将无法启用!")
return
end
if not fs.access(srss_path) then
fs.mkdir(srss_path)
end
if next(geosite_all_tag) and fs.access(geosite_path) then
for k,v in pairs(geosite_all_tag) do
local srs_file = srss_path .. "geosite-" .. k ..".srs"
if not fs.access(srs_file) then
sys.exec("geoview -type geosite -action convert -input " .. geosite_path .. " -list '" .. k .. "' -output " .. srs_file .. " -lowmem=true")
--api.log("* 转换geosite:" .. k .. " 到 Sing-Box 规则集二进制文件")
end
end
end
if next(geoip_all_tag) and fs.access(geoip_path) then
for k,v in pairs(geoip_all_tag) do
local srs_file = srss_path .. "geoip-" .. k ..".srs"
if not fs.access(srs_file) then
sys.exec("geoview -type geoip -action convert -input " .. geoip_path .. " -list '" .. k .. "' -output " .. srs_file .. " -lowmem=true")
--api.log("* 转换geoip:" .. k .. " 到 Sing-Box 规则集二进制文件")
end
end
end
end
local new_port
local function get_new_port()
@@ -802,17 +837,7 @@ function gen_config(var)
local singbox_settings = uci:get_all(appname, "@global_singbox[0]") or {}
local route = {
rules = {},
geoip = {
path = singbox_settings.geoip_path or "/usr/share/singbox/geoip.db",
download_url = singbox_settings.geoip_url or nil,
download_detour = nil,
},
geosite = {
path = singbox_settings.geosite_path or "/usr/share/singbox/geosite.db",
download_url = singbox_settings.geosite_url or nil,
download_detour = nil,
},
rules = {}
}
local experimental = nil
@@ -1183,17 +1208,21 @@ function gen_config(var)
end
if e.source then
local source_geoip = {}
local source_ip_cidr = {}
local is_private = false
string.gsub(e.source, '[^' .. " " .. ']+', function(w)
if w:find("geoip") == 1 then
table.insert(source_geoip, w)
local _geoip = w:sub(1 + #"geoip:") --适配srs
if _geoip == "private" then
is_private = true
end
else
table.insert(source_ip_cidr, w)
end
end)
rule.source_geoip = #source_geoip > 0 and source_geoip or nil
rule.source_ip_is_private = is_private and true or nil
rule.source_ip_cidr = #source_ip_cidr > 0 and source_ip_cidr or nil
if is_private or #source_ip_cidr > 0 then rule.rule_set_ip_cidr_match_source = true end
end
if e.sourcePort then
@@ -1224,6 +1253,8 @@ function gen_config(var)
rule.port_range = #port_range > 0 and port_range or nil
end
local rule_set_tag = {}
if e.domain_list then
local domain_table = {
outboundTag = outboundTag,
@@ -1231,12 +1262,15 @@ function gen_config(var)
domain_suffix = {},
domain_keyword = {},
domain_regex = {},
geosite = {},
rule_set = {},
}
string.gsub(e.domain_list, '[^' .. "\r\n" .. ']+', function(w)
if w:find("#") == 1 then return end
if w:find("geosite:") == 1 then
table.insert(domain_table.geosite, w:sub(1 + #"geosite:"))
local _geosite = w:sub(1 + #"geosite:") --适配srs
geosite_all_tag[_geosite] = true
table.insert(rule_set_tag, "geosite-" .. _geosite)
table.insert(domain_table.rule_set, "geosite-" .. _geosite)
elseif w:find("regexp:") == 1 then
table.insert(domain_table.domain_regex, w:sub(1 + #"regexp:"))
elseif w:find("full:") == 1 then
@@ -1251,7 +1285,6 @@ function gen_config(var)
rule.domain_suffix = #domain_table.domain_suffix > 0 and domain_table.domain_suffix or nil
rule.domain_keyword = #domain_table.domain_keyword > 0 and domain_table.domain_keyword or nil
rule.domain_regex = #domain_table.domain_regex > 0 and domain_table.domain_regex or nil
rule.geosite = #domain_table.geosite > 0 and domain_table.geosite or nil
if outboundTag then
table.insert(dns_domain_rules, api.clone(domain_table))
@@ -1260,20 +1293,28 @@ function gen_config(var)
if e.ip_list then
local ip_cidr = {}
local geoip = {}
local is_private = false
string.gsub(e.ip_list, '[^' .. "\r\n" .. ']+', function(w)
if w:find("#") == 1 then return end
if w:find("geoip:") == 1 then
table.insert(geoip, w:sub(1 + #"geoip:"))
local _geoip = w:sub(1 + #"geoip:") --适配srs
if _geoip == "private" then
is_private = true
else
geoip_all_tag[_geoip] = true
table.insert(rule_set_tag, "geoip-" .. _geoip)
end
else
table.insert(ip_cidr, w)
end
end)
rule.ip_is_private = is_private and true or nil
rule.ip_cidr = #ip_cidr > 0 and ip_cidr or nil
rule.geoip = #geoip > 0 and geoip or nil
end
rule.rule_set = #rule_set_tag > 0 and rule_set_tag or nil --适配srs
table.insert(rules, rule)
end
end)
@@ -1281,6 +1322,34 @@ function gen_config(var)
for index, value in ipairs(rules) do
table.insert(route.rules, rules[index])
end
local rule_set = {} --适配srs
if next(geosite_all_tag) then
for k,v in pairs(geosite_all_tag) do
local srs_file = srss_path .. "geosite-" .. k ..".srs"
local _rule_set = {
tag = "geosite-" .. k,
type = "local",
format = "binary",
path = srs_file
}
table.insert(rule_set, _rule_set)
end
end
if next(geoip_all_tag) then
for k,v in pairs(geoip_all_tag) do
local srs_file = srss_path .. "geoip-" .. k ..".srs"
local _rule_set = {
tag = "geoip-" .. k,
type = "local",
format = "binary",
path = srs_file
}
table.insert(rule_set, _rule_set)
end
end
route.rule_set = #rule_set >0 and rule_set or nil
elseif node.protocol == "_urltest" then
if node.urltest_node then
COMMON.default_outbound_tag = gen_urltest(node)
@@ -1470,14 +1539,14 @@ function gen_config(var)
--按分流顺序DNS
if dns_domain_rules and #dns_domain_rules > 0 then
for index, value in ipairs(dns_domain_rules) do
if value.outboundTag and (value.domain or value.domain_suffix or value.domain_keyword or value.domain_regex or value.geosite) then
if value.outboundTag and (value.domain or value.domain_suffix or value.domain_keyword or value.domain_regex or value.rule_set) then
local dns_rule = {
server = value.outboundTag,
domain = (value.domain and #value.domain > 0) and value.domain or nil,
domain_suffix = (value.domain_suffix and #value.domain_suffix > 0) and value.domain_suffix or nil,
domain_keyword = (value.domain_keyword and #value.domain_keyword > 0) and value.domain_keyword or nil,
domain_regex = (value.domain_regex and #value.domain_regex > 0) and value.domain_regex or nil,
geosite = (value.geosite and #value.geosite > 0) and value.geosite or nil,
rule_set = (value.rule_set and #value.rule_set > 0) and value.rule_set or nil, --适配srs
disable_cache = false,
}
if value.outboundTag ~= "block" and value.outboundTag ~= "direct" then
@@ -1737,5 +1806,8 @@ if arg[1] then
local func =_G[arg[1]]
if func then
print(func(api.get_function_args(arg)))
if next(geosite_all_tag) or next(geoip_all_tag) then
convert_geofile()
end
end
end

View File

@@ -937,8 +937,14 @@ msgstr "小时"
msgid "Hour"
msgstr "小时"
msgid "Location of V2ray/Xray asset"
msgstr "V2ray/Xray 资源文件目录"
msgid "GeoIP Update URL"
msgstr "GeoIP 更新URL"
msgid "Geosite Update URL"
msgstr "Geosite 更新URL"
msgid "Location of Geo rule files"
msgstr "Geo 规则文件目录"
msgid "This variable specifies a directory where geoip.dat and geosite.dat files are."
msgstr "此变量指定 geoip.dat 和 geosite.dat 文件所在的目录。"
@@ -1633,24 +1639,6 @@ msgstr "端口跳跃额外端口"
msgid "HeartbeatPeriod(second)"
msgstr "心跳周期(单位:秒)"
msgid "Custom geoip Path"
msgstr "自定义 geoip 文件路径"
msgid "Custom geoip URL"
msgstr "自定义 geoip 文件更新链接"
msgid "Custom geosite Path"
msgstr "自定义 geosite 文件路径"
msgid "Custom geosite URL"
msgstr "自定义 geosite 文件更新链接"
msgid "Remove resource files"
msgstr "删除资源文件"
msgid "Sing-Box will automatically download resource files when starting, you can use this feature achieve upgrade resource files."
msgstr "Sing-Box 会在启动时自动下载资源文件,您可以使用此功能实现升级资源文件。"
msgid "Override the connection destination address"
msgstr "覆盖连接目标地址"

View File

@@ -51,10 +51,6 @@ config global_xray
config global_singbox
option sniff_override_destination '0'
option geoip_path '/usr/share/singbox/geoip.db'
option geoip_url 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.db'
option geosite_path '/usr/share/singbox/geosite.db'
option geosite_url 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.db'
config global_other
option auto_detection_time 'tcping'
@@ -76,6 +72,8 @@ config global_rules
list chnlist_url 'https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf'
list chnlist_url 'https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf'
option v2ray_location_asset '/usr/share/v2ray/'
option geoip_url 'https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest'
option geosite_url 'https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest'
config global_app
option singbox_file '/usr/bin/sing-box'

View File

@@ -350,8 +350,7 @@ parse_doh() {
get_geoip() {
local geoip_code="$1"
local geoip_type_flag=""
local geoip_path="$(config_t_get global_rules v2ray_location_asset)"
geoip_path="${geoip_path%*/}/geoip.dat"
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
[ -s "$geoip_path" ] || { echo ""; return 1; }
case "$2" in
"ipv4") geoip_type_flag="-ipv6=false" ;;
@@ -777,8 +776,8 @@ run_redir() {
sing-box)
local protocol=$(config_n_get $node protocol)
[ "$protocol" = "_shunt" ] && {
local geoip_path="$(config_t_get global_singbox geoip_path)"
local geosite_path="$(config_t_get global_singbox geosite_path)"
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
local geosite_path="${V2RAY_LOCATION_ASSET%*/}/geosite.dat"
if [ ! -s "$geoip_path" ] || [ ! -s "$geosite_path" ]; then
echolog "* 缺少Geo规则文件UDP Sing-Box分流节点无法正常使用"
fi
@@ -788,9 +787,8 @@ run_redir() {
xray)
local protocol=$(config_n_get $node protocol)
[ "$protocol" = "_shunt" ] && {
local geo_path="$(config_t_get global_rules v2ray_location_asset)"
local geoip_path="${geo_path%*/}/geoip.dat"
local geosite_path="${geo_path%*/}/geosite.dat"
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
local geosite_path="${V2RAY_LOCATION_ASSET%*/}/geosite.dat"
if [ ! -s "$geoip_path" ] || [ ! -s "$geosite_path" ]; then
echolog "* 缺少Geo规则文件UDP Xray分流节点无法正常使用"
fi
@@ -895,8 +893,8 @@ run_redir() {
}
[ "$protocol" = "_shunt" ] && {
local geoip_path="$(config_t_get global_singbox geoip_path)"
local geosite_path="$(config_t_get global_singbox geosite_path)"
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
local geosite_path="${V2RAY_LOCATION_ASSET%*/}/geosite.dat"
if [ ! -s "$geoip_path" ] || [ ! -s "$geosite_path" ]; then
echolog "* 缺少Geo规则文件TCP Sing-Box分流节点无法正常使用"
fi
@@ -981,9 +979,8 @@ run_redir() {
}
[ "$protocol" = "_shunt" ] && {
local geo_path="$(config_t_get global_rules v2ray_location_asset)"
local geoip_path="${geo_path%*/}/geoip.dat"
local geosite_path="${geo_path%*/}/geosite.dat"
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
local geosite_path="${V2RAY_LOCATION_ASSET%*/}/geosite.dat"
if [ ! -s "$geoip_path" ] || [ ! -s "$geosite_path" ]; then
echolog "* 缺少Geo规则文件TCP Xray分流节点无法正常使用"
fi

View File

@@ -88,7 +88,7 @@ local function insert_array_after(array1, array2, target) --将array2插入到ar
end
local function get_geosite(list_arg, out_path)
local geosite_path = uci:get(appname, "@global_rules[0]", "v2ray_location_asset")
local geosite_path = uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"
geosite_path = geosite_path:match("^(.*)/") .. "/geosite.dat"
if not is_file_nonzero(geosite_path) then return 1 end
if api.is_finded("geoview") and list_arg and out_path then

View File

@@ -92,7 +92,7 @@ local function insert_array_after(array1, array2, target) --将array2插入到ar
end
local function get_geosite(list_arg, out_path)
local geosite_path = uci:get(appname, "@global_rules[0]", "v2ray_location_asset")
local geosite_path = uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"
geosite_path = geosite_path:match("^(.*)/") .. "/geosite.dat"
if not is_file_nonzero(geosite_path) then return 1 end
if api.is_finded("geoview") and list_arg and out_path then

View File

@@ -33,8 +33,8 @@ local gfwlist_url = uci:get(name, "@global_rules[0]", "gfwlist_url") or {"https:
local chnroute_url = uci:get(name, "@global_rules[0]", "chnroute_url") or {"https://ispip.clang.cn/all_cn.txt"}
local chnroute6_url = uci:get(name, "@global_rules[0]", "chnroute6_url") or {"https://ispip.clang.cn/all_cn_ipv6.txt"}
local chnlist_url = uci:get(name, "@global_rules[0]", "chnlist_url") or {"https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf","https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf","https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/google.china.conf"}
local geoip_api = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
local geosite_api = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
local geoip_api = uci:get(name, "@global_rules[0]", "geoip_url") or "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
local geosite_api = uci:get(name, "@global_rules[0]", "geosite_url") or "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
local asset_location = uci:get(name, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"
local use_nft = uci:get(name, "@global_forwarding[0]", "use_nft") or "0"

View File

@@ -7,33 +7,20 @@ namespace AmazTool
{
if (args.Length == 0)
{
Utils.WriteLine(Resx.Resource.Guidelines);
Utils.Waiting(5);
Console.WriteLine(Resx.Resource.Guidelines);
Thread.Sleep(5000);
return;
}
var argData = Uri.UnescapeDataString(string.Join(" ", args));
if (argData.Equals("rebootas"))
{
Utils.Waiting(1);
Thread.Sleep(1000);
Utils.StartV2RayN();
return;
}
var tryTimes = 0;
UpgradeApp.Init();
while (tryTimes++ < 3)
{
if (!UpgradeApp.Upgrade(argData))
{
continue;
}
Utils.WriteLine(Resx.Resource.Restartv2rayN);
Utils.Waiting(3);
Utils.StartV2RayN();
break;
}
UpgradeApp.Upgrade(argData);
}
}
}

View File

@@ -6,99 +6,19 @@ namespace AmazTool
{
internal class UpgradeApp
{
public static bool Upgrade(string fileName)
public static void Upgrade(string fileName)
{
Utils.WriteLine($"{Resx.Resource.StartUnzipping}\n{fileName}");
if (!File.Exists(fileName))
{
Utils.WriteLine(Resx.Resource.UpgradeFileNotFound);
return false;
}
Console.WriteLine($"{Resx.Resource.StartUnzipping}\n{fileName}");
Utils.Waiting(5);
KillV2rayN();
Utils.WriteLine(Resx.Resource.StartUnzipping);
StringBuilder sb = new();
try
if (!File.Exists(fileName))
{
var splitKey = "/";
using var archive = ZipFile.OpenRead(fileName);
foreach (var entry in archive.Entries)
{
try
{
if (entry.Length == 0)
{
continue;
}
Utils.WriteLine(entry.FullName);
var lst = entry.FullName.Split(splitKey);
if (lst.Length == 1)
{
continue;
}
var fullName = string.Join(splitKey, lst[1..lst.Length]);
var entryOutputPath = Utils.GetPath(fullName);
Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!);
//In the bin folder, if the file already exists, it will be skipped
if (fullName.StartsWith("bin") && File.Exists(entryOutputPath))
{
continue;
}
entry.ExtractToFile(entryOutputPath, true);
Utils.WriteLine(entryOutputPath);
}
catch (Exception ex)
{
sb.Append(ex.Message);
sb.Append(ex.StackTrace);
}
}
}
catch (Exception ex)
{
sb.Append(Resx.Resource.FailedUpgrade + ex.StackTrace);
Console.WriteLine(Resx.Resource.UpgradeFileNotFound);
return;
}
if (sb.Length <= 0)
{
return true;
}
Utils.WriteLine(sb.ToString());
Utils.WriteLine(Resx.Resource.FailedUpgrade);
return false;
}
public static bool Init()
{
//Process temporary files generated by the last update
var files = Directory.GetFiles(Utils.GetPath(""), "*.tmp");
foreach (var file in files)
{
if (file.Contains(Utils.AmazTool))
{
File.Delete(file);
}
}
var destFileName = $"{Utils.GetExePath()}{Guid.NewGuid().ToString("N")[..8]}.tmp";
File.Move(Utils.GetExePath(), destFileName);
return true;
}
private static bool KillV2rayN()
{
Utils.WriteLine(Resx.Resource.TryTerminateProcess);
Console.WriteLine(Resx.Resource.TryTerminateProcess);
try
{
var existing = Process.GetProcessesByName(Utils.V2rayN);
@@ -115,10 +35,83 @@ namespace AmazTool
catch (Exception ex)
{
// Access may be denied without admin right. The user may not be an administrator.
Utils.WriteLine(Resx.Resource.FailedTerminateProcess + ex.StackTrace);
Console.WriteLine(Resx.Resource.FailedTerminateProcess + ex.StackTrace);
}
return true;
Console.WriteLine(Resx.Resource.StartUnzipping);
StringBuilder sb = new();
try
{
var thisAppOldFile = $"{Utils.GetExePath()}.tmp";
File.Delete(thisAppOldFile);
var splitKey = "/";
using var archive = ZipFile.OpenRead(fileName);
foreach (var entry in archive.Entries)
{
try
{
if (entry.Length == 0)
{
continue;
}
Console.WriteLine(entry.FullName);
var lst = entry.FullName.Split(splitKey);
if (lst.Length == 1)
{
continue;
}
var fullName = string.Join(splitKey, lst[1..lst.Length]);
if (string.Equals(Utils.GetExePath(), Utils.GetPath(fullName), StringComparison.OrdinalIgnoreCase))
{
File.Move(Utils.GetExePath(), thisAppOldFile);
}
var entryOutputPath = Utils.GetPath(fullName);
Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!);
//In the bin folder, if the file already exists, it will be skipped
if (fullName.StartsWith("bin") && File.Exists(entryOutputPath))
{
continue;
}
try
{
entry.ExtractToFile(entryOutputPath, true);
}
catch
{
Thread.Sleep(1000);
entry.ExtractToFile(entryOutputPath, true);
}
Console.WriteLine(entryOutputPath);
}
catch (Exception ex)
{
sb.Append(ex.StackTrace);
}
}
}
catch (Exception ex)
{
Console.WriteLine(Resx.Resource.FailedUpgrade + ex.StackTrace);
//return;
}
if (sb.Length > 0)
{
Console.WriteLine(Resx.Resource.FailedUpgrade + sb.ToString());
//return;
}
Console.WriteLine(Resx.Resource.Restartv2rayN);
Utils.Waiting(2);
Utils.StartV2RayN();
}
}
}

View File

@@ -14,7 +14,7 @@ namespace AmazTool
return AppDomain.CurrentDomain.BaseDirectory;
}
public static string GetPath(string? fileName)
public static string GetPath(string fileName)
{
var startupPath = StartupPath();
if (string.IsNullOrEmpty(fileName))
@@ -25,7 +25,6 @@ namespace AmazTool
}
public static string V2rayN => "v2rayN";
public static string AmazTool => "AmazTool";
public static void StartV2RayN()
{
@@ -45,14 +44,9 @@ namespace AmazTool
{
for (var i = second; i > 0; i--)
{
Utils.WriteLine(i);
Console.WriteLine(i);
Thread.Sleep(1000);
}
}
public static void WriteLine(object obj)
{
Console.WriteLine(obj);
}
}
}

View File

@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.10.1</Version>
<Version>7.10.3</Version>
</PropertyGroup>
<PropertyGroup>

View File

@@ -265,7 +265,8 @@ namespace ServiceLib
"utp",
"wechat-video",
"dtls",
"wireguard"
"wireguard",
"dns"
];
public static readonly List<string> CoreTypes =

View File

@@ -109,7 +109,7 @@ namespace ServiceLib.Handler
task.Settings.RunOnlyIfIdle = false;
task.Settings.IdleSettings.StopOnIdleEnd = false;
task.Settings.ExecutionTimeLimit = TimeSpan.Zero;
task.Triggers.Add(new Microsoft.Win32.TaskScheduler.LogonTrigger { UserId = logonUser, Delay = TimeSpan.FromSeconds(20) });
task.Triggers.Add(new Microsoft.Win32.TaskScheduler.LogonTrigger { UserId = logonUser, Delay = TimeSpan.FromSeconds(30) });
task.Principal.RunLevel = Microsoft.Win32.TaskScheduler.TaskRunLevel.Highest;
task.Actions.Add(new Microsoft.Win32.TaskScheduler.ExecAction(fileName.AppendQuotes(), null, Path.GetDirectoryName(fileName)));

View File

@@ -291,6 +291,8 @@ namespace ServiceLib.Models
public object request { get; set; }
public object response { get; set; }
public string? domain { get; set; }
}
public class KcpSettings4Ray

View File

@@ -915,7 +915,8 @@ namespace ServiceLib.Services.CoreConfig
kcpSettings.writeBufferSize = _config.KcpItem.WriteBufferSize;
kcpSettings.header = new Header4Ray
{
type = node.HeaderType
type = node.HeaderType,
domain = host.IsNullOrEmpty() ? null : host
};
if (Utils.IsNotEmpty(path))
{

View File

@@ -3,7 +3,7 @@ module github.com/2dust/AndroidLibXrayLite
go 1.24
require (
github.com/xtls/xray-core v1.8.25-0.20250221075831-be43f66b63d5
github.com/xtls/xray-core v1.8.25-0.20250303153022-e15dff94b5bd
golang.org/x/mobile v0.0.0-20250218173827-cd096645fcd3
golang.org/x/sys v0.30.0
)
@@ -36,7 +36,7 @@ require (
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 // indirect
go.uber.org/mock v0.5.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/net v0.35.0 // indirect
@@ -51,5 +51,5 @@ require (
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gvisor.dev/gvisor v0.0.0-20250215002057-313350f3e697 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
lukechampine.com/blake3 v1.4.0 // indirect
)

View File

@@ -24,8 +24,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20250208200701-d0013a598941 h1:43XjGa6toxLpeksjcxs1jIoIyr+vUfOqY2c6HB4bpoc=
github.com/google/pprof v0.0.0-20250208200701-d0013a598941/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -75,8 +75,8 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 h1:g1Cj7d+my6k/HHxLAyxPwyX8i7FGRr6ulBDMkBzg2BM=
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463/go.mod h1:BjIOLmkEEtAgloAiVUcYj0Mt+YU00JARZw8AEU0IwAg=
github.com/xtls/xray-core v1.8.25-0.20250221075831-be43f66b63d5 h1:x4pNCiwk/x/zS/dpZl8fdNzC0YP8NBIOVZOAA1in/Zo=
github.com/xtls/xray-core v1.8.25-0.20250221075831-be43f66b63d5/go.mod h1:bPLUPQGZ8ADeL1CANo0nIUNchY+SuhZCT+ItgprlPf4=
github.com/xtls/xray-core v1.8.25-0.20250303153022-e15dff94b5bd h1:xzZCYhdr1pL1kZe7GysAMuVNtlzXqsQKTFbc1xvR+bI=
github.com/xtls/xray-core v1.8.25-0.20250303153022-e15dff94b5bd/go.mod h1:0n4A2nJD1yZlxuXexV5rJODKcJJo8zpbTFcESVg8fgM=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
@@ -93,8 +93,8 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
golang.org/x/mobile v0.0.0-20250218173827-cd096645fcd3 h1:0V/7Y1FEaFdAzb9DkVDh4QFp4vL4yYCiJ5cjk80lZyA=
@@ -136,5 +136,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20250215002057-313350f3e697 h1:3xb9C+AmVuRnDDAwJGJ8ZVAmcIourUD9DwHaAd5Ldyk=
gvisor.dev/gvisor v0.0.0-20250215002057-313350f3e697/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=
lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=

View File

@@ -12,8 +12,8 @@ android {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 35
versionCode = 634
versionName = "1.9.37"
versionCode = 635
versionName = "1.9.38"
multiDexEnabled = true
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
@@ -157,9 +157,8 @@ dependencies {
implementation(libs.gson)
// Reactive and Utility Libraries
implementation(libs.rxjava)
implementation(libs.rxandroid)
implementation(libs.rxpermissions)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.coroutines.core)
// Language and Processing Libraries
implementation(libs.language.base)

View File

@@ -31,10 +31,11 @@ import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.PluginUtil
import com.v2ray.ang.util.Utils
import go.Seq
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import libv2ray.Libv2ray
import libv2ray.V2RayPoint
@@ -62,7 +63,7 @@ object V2RayServiceManager {
private var lastQueryTime = 0L
private var mBuilder: NotificationCompat.Builder? = null
private var mDisposable: Disposable? = null
private var speedNotificationJob: Job? = null
private var mNotificationManager: NotificationManager? = null
fun startV2Ray(context: Context) {
@@ -365,8 +366,8 @@ object V2RayServiceManager {
}
mBuilder = null
mDisposable?.dispose()
mDisposable = null
speedNotificationJob?.cancel()
speedNotificationJob = null
}
private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) {
@@ -393,7 +394,7 @@ object V2RayServiceManager {
}
private fun startSpeedNotification() {
if (mDisposable == null &&
if (speedNotificationJob == null &&
v2rayPoint.isRunning &&
MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) == true
) {
@@ -401,8 +402,8 @@ object V2RayServiceManager {
val outboundTags = currentConfig?.getAllOutboundTags()
outboundTags?.remove(TAG_DIRECT)
mDisposable = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
.subscribe {
speedNotificationJob = CoroutineScope(Dispatchers.IO).launch {
while (isActive) {
val queryTime = System.currentTimeMillis()
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
var proxyTotal = 0L
@@ -430,7 +431,9 @@ object V2RayServiceManager {
}
lastZeroSpeed = zeroSpeed
lastQueryTime = queryTime
delay(3000)
}
}
}
}
@@ -445,11 +448,10 @@ object V2RayServiceManager {
}
private fun stopSpeedNotification() {
mDisposable?.let {
it.dispose() //stop queryStats
mDisposable = null
speedNotificationJob?.let {
it.cancel()
speedNotificationJob = null
updateNotification(currentConfig?.remarks, 0, 0)
}
}
}

View File

@@ -5,15 +5,16 @@ import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import com.tbruyelle.rxpermissions3.RxPermissions
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityAboutBinding
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.SpeedtestUtil
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.ZipUtil
@@ -21,11 +22,24 @@ import java.io.File
import java.text.SimpleDateFormat
import java.util.Locale
class AboutActivity : BaseActivity() {
private val binding by lazy { ActivityAboutBinding.inflate(layoutInflater) }
private val extDir by lazy { File(Utils.backupPath(this)) }
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
try {
showFileChooser()
} catch (e: Exception) {
e.printStackTrace()
}
} else {
toast(R.string.toast_permission_denied)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
@@ -33,6 +47,7 @@ class AboutActivity : BaseActivity() {
title = getString(R.string.title_about)
binding.tvBackupSummary.text = this.getString(R.string.summary_configuration_backup, extDir)
binding.layoutBackup.setOnClickListener {
val ret = backupConfiguration(extDir.absolutePath)
if (ret.first) {
@@ -50,7 +65,8 @@ class AboutActivity : BaseActivity() {
Intent(Intent.ACTION_SEND).setType("application/zip")
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.putExtra(
Intent.EXTRA_STREAM, FileProvider.getUriForFile(
Intent.EXTRA_STREAM,
FileProvider.getUriForFile(
this, BuildConfig.APPLICATION_ID + ".cache", File(ret.second)
)
), getString(R.string.title_configuration_share)
@@ -62,23 +78,22 @@ class AboutActivity : BaseActivity() {
}
binding.layoutRestore.setOnClickListener {
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
RxPermissions(this)
.request(permission)
.subscribe {
if (it) {
try {
showFileChooser()
} catch (e: Exception) {
e.printStackTrace()
}
} else
toast(R.string.toast_permission_denied)
val permission =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
if (ContextCompat.checkSelfPermission(this, permission) == android.content.pm.PackageManager.PERMISSION_GRANTED) {
try {
showFileChooser()
} catch (e: Exception) {
e.printStackTrace()
}
} else {
requestPermissionLauncher.launch(permission)
}
}
binding.layoutSoureCcode.setOnClickListener {
@@ -88,13 +103,15 @@ class AboutActivity : BaseActivity() {
binding.layoutFeedback.setOnClickListener {
Utils.openUri(this, AppConfig.v2rayNGIssues)
}
binding.layoutOssLicenses.setOnClickListener{
binding.layoutOssLicenses.setOnClickListener {
val webView = android.webkit.WebView(this);
webView.loadUrl("file:///android_asset/open_source_licenses.html");
webView.loadUrl("file:///android_asset/open_source_licenses.html")
android.app.AlertDialog.Builder(this)
.setTitle("Open source licenses")
.setView(webView)
.setPositiveButton("OK", android.content.DialogInterface.OnClickListener { dialog, whichButton -> dialog.dismiss() }).show()
.setPositiveButton("OK") { dialog, _ -> dialog.dismiss() }
.show()
}
binding.layoutTgChannel.setOnClickListener {
@@ -157,9 +174,9 @@ class AboutActivity : BaseActivity() {
}
private val chooseFile =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val uri = it.data?.data
if (it.resultCode == RESULT_OK && uri != null) {
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val uri = result.data?.data
if (result.resultCode == RESULT_OK && uri != null) {
try {
val targetFile =
File(this.cacheDir.absolutePath, "${System.currentTimeMillis()}.zip")
@@ -180,4 +197,7 @@ class AboutActivity : BaseActivity() {
}
}
private fun toast(messageResId: Int) {
Toast.makeText(this, getString(messageResId), Toast.LENGTH_SHORT).show()
}
}

View File

@@ -3,6 +3,7 @@ package com.v2ray.ang.ui
import android.Manifest
import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.ColorStateList
import android.net.Uri
import android.net.VpnService
@@ -27,7 +28,6 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.navigation.NavigationView
import com.google.android.material.tabs.TabLayout
import com.tbruyelle.rxpermissions3.RxPermissions
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.VPN
import com.v2ray.ang.R
@@ -41,8 +41,6 @@ import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.MainViewModel
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -81,6 +79,60 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
private var mItemTouchHelper: ItemTouchHelper? = null
val mainViewModel: MainViewModel by viewModels()
// register activity result for requesting permission
private val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
when (pendingAction) {
Action.IMPORT_QR_CODE_CONFIG ->
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
Action.IMPORT_QR_CODE_URL ->
scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java))
Action.READ_CONTENT_FROM_URI ->
chooseFileForCustomConfig.launch(Intent.createChooser(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "*/*"
addCategory(Intent.CATEGORY_OPENABLE)
}, getString(R.string.title_file_chooser)))
Action.POST_NOTIFICATIONS -> {}
else -> {}
}
} else {
toast(R.string.toast_permission_denied)
}
pendingAction = Action.NONE
}
private var pendingAction: Action = Action.NONE
enum class Action {
NONE,
IMPORT_QR_CODE_CONFIG,
IMPORT_QR_CODE_URL,
READ_CONTENT_FROM_URI,
POST_NOTIFICATIONS
}
private val chooseFileForCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val uri = it.data?.data
if (it.resultCode == RESULT_OK && uri != null) {
readContentFromUri(uri)
}
}
private val scanQRCodeForConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"))
}
}
private val scanQRCodeForUrlToCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
importConfigCustomUrl(it.data?.getStringExtra("SCAN_RESULT"))
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
@@ -129,12 +181,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
migrateLegacy()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
RxPermissions(this)
.request(Manifest.permission.POST_NOTIFICATIONS)
.subscribe {
if (!it)
toast(R.string.toast_permission_denied_notification)
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
pendingAction = Action.POST_NOTIFICATIONS
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
@@ -226,11 +276,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
if (mainViewModel.isRunning.value == true) {
Utils.stopVService(this)
}
Observable.timer(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
startV2Ray()
}
lifecycleScope.launch {
delay(500)
startV2Ray()
}
}
public override fun onResume() {
@@ -462,38 +511,20 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
* import config from qrcode
*/
private fun importQRcode(forConfig: Boolean): Boolean {
// try {
// startActivityForResult(Intent("com.google.zxing.client.android.SCAN")
// .addCategory(Intent.CATEGORY_DEFAULT)
// .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), requestCode)
// } catch (e: Exception) {
RxPermissions(this)
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
if (forConfig)
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
else
scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java))
else
toast(R.string.toast_permission_denied)
val permission = Manifest.permission.CAMERA
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
if (forConfig) {
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
} else {
scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java))
}
// }
} else {
pendingAction = if (forConfig) Action.IMPORT_QR_CODE_CONFIG else Action.IMPORT_QR_CODE_URL
requestPermissionLauncher.launch(permission)
}
return true
}
private val scanQRCodeForConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"))
}
}
private val scanQRCodeForUrlToCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
importConfigCustomUrl(it.data?.getStringExtra("SCAN_RESULT"))
}
}
/**
* import config from clipboard
*/
@@ -614,10 +645,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
* import config from sub
*/
private fun importConfigViaSub(): Boolean {
// val dialog = AlertDialog.Builder(this)
// .setView(LayoutProgressBinding.inflate(layoutInflater).root)
// .setCancelable(false)
// .show()
binding.pbWaiting.show()
lifecycleScope.launch(Dispatchers.IO) {
@@ -630,7 +657,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} else {
toast(R.string.toast_failure)
}
//dialog.dismiss()
binding.pbWaiting.hide()
}
}
@@ -645,17 +671,17 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
intent.type = "*/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
try {
chooseFileForCustomConfig.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
} catch (ex: ActivityNotFoundException) {
toast(R.string.toast_require_file_manager)
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
}
private val chooseFileForCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val uri = it.data?.data
if (it.resultCode == RESULT_OK && uri != null) {
readContentFromUri(uri)
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
pendingAction = Action.READ_CONTENT_FROM_URI
chooseFileForCustomConfig.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
} else {
requestPermissionLauncher.launch(permission)
}
}
@@ -668,20 +694,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
RxPermissions(this)
.request(permission)
.subscribe {
if (it) {
try {
contentResolver.openInputStream(uri).use { input ->
importCustomizeConfig(input?.bufferedReader()?.readText())
}
} catch (e: Exception) {
e.printStackTrace()
}
} else
toast(R.string.toast_permission_denied)
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
try {
contentResolver.openInputStream(uri).use { input ->
importCustomizeConfig(input?.bufferedReader()?.readText())
}
} catch (e: Exception) {
e.printStackTrace()
}
} else {
requestPermissionLauncher.launch(permission)
}
}
/**
@@ -763,4 +787,4 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
binding.drawerLayout.closeDrawer(GravityCompat.START)
return true
}
}
}

View File

@@ -8,6 +8,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import com.v2ray.ang.AngApplication.Companion.application
import com.v2ray.ang.AppConfig
@@ -23,9 +24,9 @@ import com.v2ray.ang.helper.ItemTouchHelperAdapter
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.Utils
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.*
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>(), ItemTouchHelperAdapter {
companion object {
@@ -165,11 +166,14 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
notifyItemChanged(mActivity.mainViewModel.getPosition(guid))
if (isRunning) {
Utils.stopVService(mActivity)
Observable.timer(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
mActivity.lifecycleScope.launch {
try {
delay(500)
V2RayServiceManager.startV2Ray(mActivity)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
}
@@ -246,4 +250,4 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
override fun onItemDismiss(position: Int) {
}
}
}

View File

@@ -20,10 +20,9 @@ import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.AppManagerUtil
import com.v2ray.ang.util.Utils
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.text.Collator
class PerAppProxyActivity : BaseActivity() {
@@ -43,93 +42,39 @@ class PerAppProxyActivity : BaseActivity() {
val blacklist = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
AppManagerUtil.rxLoadNetworkAppList(this)
.subscribeOn(Schedulers.io())
.map {
if (blacklist != null) {
it.forEach { one ->
if (blacklist.contains(one.packageName)) {
one.isSelected = 1
} else {
one.isSelected = 0
lifecycleScope.launch {
try {
binding.pbWaiting.visibility = View.VISIBLE
val blacklist = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
val apps = withContext(Dispatchers.IO) {
val appsList = AppManagerUtil.loadNetworkAppList(this@PerAppProxyActivity)
if (blacklist != null) {
appsList.forEach { app ->
app.isSelected = if (blacklist.contains(app.packageName)) 1 else 0
}
}
val comparator = Comparator<AppInfo> { p1, p2 ->
when {
p1.isSelected > p2.isSelected -> -1
p1.isSelected == p2.isSelected -> 0
else -> 1
}
}
it.sortedWith(comparator)
} else {
val comparator = object : Comparator<AppInfo> {
appsList.sortedWith(Comparator { p1, p2 ->
when {
p1.isSelected > p2.isSelected -> -1
p1.isSelected == p2.isSelected -> 0
else -> 1
}
})
} else {
val collator = Collator.getInstance()
override fun compare(o1: AppInfo, o2: AppInfo) = collator.compare(o1.appName, o2.appName)
appsList.sortedWith(compareBy(collator) { it.appName })
}
it.sortedWith(comparator)
}
}
// .map {
// val comparator = object : Comparator<AppInfo> {
// val collator = Collator.getInstance()
// override fun compare(o1: AppInfo, o2: AppInfo) = collator.compare(o1.appName, o2.appName)
// }
// it.sortedWith(comparator)
// }
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
appsAll = it
adapter = PerAppProxyAdapter(this, it, blacklist)
appsAll = apps
adapter = PerAppProxyAdapter(this@PerAppProxyActivity, apps, blacklist)
binding.recyclerView.adapter = adapter
binding.pbWaiting.visibility = View.GONE
} catch (e: Exception) {
binding.pbWaiting.visibility = View.GONE
Log.e(ANG_PACKAGE, "Error loading apps", e)
}
/***
recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() {
var dst = 0
val threshold = resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 2
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
dst += dy
if (dst > threshold) {
header_view.hide()
dst = 0
} else if (dst < -20) {
header_view.show()
dst = 0
}
}
var hiding = false
fun View.hide() {
val target = -height.toFloat()
if (hiding || translationY == target) return
animate()
.translationY(target)
.setInterpolator(AccelerateInterpolator(2F))
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
hiding = false
}
})
hiding = true
}
var showing = false
fun View.show() {
val target = 0f
if (showing || translationY == target) return
animate()
.translationY(target)
.setInterpolator(DecelerateInterpolator(2F))
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
showing = false
}
})
showing = true
}
})
***/
binding.switchPerAppProxy.setOnCheckedChangeListener { _, isChecked ->
MmkvManager.encodeSettings(AppConfig.PREF_PER_APP_PROXY, isChecked)
@@ -140,36 +85,6 @@ class PerAppProxyActivity : BaseActivity() {
MmkvManager.encodeSettings(AppConfig.PREF_BYPASS_APPS, isChecked)
}
binding.switchBypassApps.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS, false)
/***
et_search.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
//hide
var imm: InputMethodManager = v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS)
val key = v.text.toString().toUpperCase()
val apps = ArrayList<AppInfo>()
if (TextUtils.isEmpty(key)) {
appsAll?.forEach {
apps.add(it)
}
} else {
appsAll?.forEach {
if (it.appName.toUpperCase().indexOf(key) >= 0) {
apps.add(it)
}
}
}
adapter = PerAppProxyAdapter(this, apps, adapter?.blacklist)
recycler_view.adapter = adapter
adapter?.notifyDataSetChanged()
true
} else {
false
}
}
***/
}
override fun onPause() {

View File

@@ -12,7 +12,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import com.tbruyelle.rxpermissions3.RxPermissions
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityRoutingSettingBinding
@@ -39,6 +38,16 @@ class RoutingSettingActivity : BaseActivity() {
private val preset_rulesets: Array<out String> by lazy {
resources.getStringArray(R.array.preset_rulesets)
}
private val requestCameraPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
scanQRcodeForRulesets.launch(Intent(this, ScannerActivity::class.java))
} else {
toast(R.string.toast_permission_denied)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -102,11 +111,9 @@ class RoutingSettingActivity : BaseActivity() {
e.printStackTrace()
}
}.show()
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
//do noting
//do nothing
}
.show()
true
@@ -142,18 +149,10 @@ class RoutingSettingActivity : BaseActivity() {
}
R.id.import_rulesets_from_qrcode -> {
RxPermissions(this)
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
scanQRcodeForRulesets.launch(Intent(this, ScannerActivity::class.java))
else
toast(R.string.toast_permission_denied)
}
requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA)
true
}
R.id.export_rulesets_to_clipboard -> {
val rulesetList = MmkvManager.decodeRoutingRulesets()
if (rulesetList.isNullOrEmpty()) {
@@ -201,5 +200,4 @@ class RoutingSettingActivity : BaseActivity() {
rulesets.addAll(MmkvManager.decodeRoutingRulesets() ?: mutableListOf())
adapter.notifyDataSetChanged()
}
}
}

View File

@@ -4,13 +4,23 @@ import android.Manifest
import android.content.Intent
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import com.tbruyelle.rxpermissions3.RxPermissions
import com.v2ray.ang.R
import com.v2ray.ang.extension.toast
import com.v2ray.ang.handler.AngConfigManager
class ScScannerActivity : BaseActivity() {
private val requestCameraPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
scanQRCode.launch(Intent(this, ScannerActivity::class.java))
} else {
toast(R.string.toast_permission_denied)
finish()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_none)
@@ -18,19 +28,10 @@ class ScScannerActivity : BaseActivity() {
}
fun importQRcode(): Boolean {
RxPermissions(this)
.request(Manifest.permission.CAMERA)
.subscribe { granted ->
if (granted) {
scanQRCode.launch(Intent(this, ScannerActivity::class.java))
} else {
toast(R.string.toast_permission_denied)
}
}
requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA)
return true
}
private val scanQRCode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
val scanResult = it.data?.getStringExtra("SCAN_RESULT").orEmpty()
@@ -46,5 +47,4 @@ class ScScannerActivity : BaseActivity() {
}
finish()
}
}
}

View File

@@ -2,13 +2,15 @@ package com.v2ray.ang.ui
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.os.Build
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts
import com.tbruyelle.rxpermissions3.RxPermissions
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.toast
@@ -21,6 +23,37 @@ import io.github.g00fy2.quickie.config.ScannerConfig
class ScannerActivity : BaseActivity() {
private val scanQrCode = registerForActivityResult(ScanCustomCode(), ::handleResult)
private val chooseFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val uri = it.data?.data
if (it.resultCode == RESULT_OK && uri != null) {
try {
val inputStream = contentResolver.openInputStream(uri)
val bitmap = BitmapFactory.decodeStream(inputStream)
inputStream?.close()
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
if (text.isNullOrEmpty()) {
toast(R.string.toast_decoding_failed)
} else {
finished(text)
}
} catch (e: Exception) {
e.printStackTrace()
toast(R.string.toast_decoding_failed)
}
}
}
private val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
showFileChooser()
} else {
toast(R.string.toast_permission_denied)
}
}
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -72,15 +105,12 @@ class ScannerActivity : BaseActivity() {
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
RxPermissions(this)
.request(permission)
.subscribe { granted ->
if (granted) {
showFileChooser()
} else {
toast(R.string.toast_permission_denied)
}
}
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
showFileChooser()
} else {
requestPermissionLauncher.launch(permission)
}
true
}
@@ -100,26 +130,4 @@ class ScannerActivity : BaseActivity() {
toast(R.string.toast_require_file_manager)
}
}
private val chooseFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val uri = it.data?.data
if (it.resultCode == RESULT_OK && uri != null) {
try {
val inputStream = contentResolver.openInputStream(uri)
val bitmap = BitmapFactory.decodeStream(inputStream)
inputStream?.close()
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
if (text.isNullOrEmpty()) {
toast(R.string.toast_decoding_failed)
} else {
finished(text)
}
} catch (e: Exception) {
e.printStackTrace()
toast(R.string.toast_decoding_failed)
}
}
}
}
}

View File

@@ -19,7 +19,6 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.tbruyelle.rxpermissions3.RxPermissions
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.R
@@ -50,6 +49,38 @@ class UserAssetActivity : BaseActivity() {
val extDir by lazy { File(Utils.userAssetPath(this)) }
val builtInGeoFiles = arrayOf("geosite.dat", "geoip.dat")
private val requestStoragePermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
try {
chooseFile.launch(
Intent.createChooser(
intent,
getString(R.string.title_file_chooser)
)
)
} catch (ex: android.content.ActivityNotFoundException) {
toast(R.string.toast_require_file_manager)
}
} else {
toast(R.string.toast_permission_denied)
}
}
private val requestCameraPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
scanQRCodeForAssetURL.launch(Intent(this, ScannerActivity::class.java))
} else {
toast(R.string.toast_permission_denied)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -86,27 +117,7 @@ class UserAssetActivity : BaseActivity() {
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
RxPermissions(this)
.request(permission)
.subscribe {
if (it) {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
try {
chooseFile.launch(
Intent.createChooser(
intent,
getString(R.string.title_file_chooser)
)
)
} catch (ex: android.content.ActivityNotFoundException) {
toast(R.string.toast_require_file_manager)
}
} else
toast(R.string.toast_permission_denied)
}
requestStoragePermissionLauncher.launch(permission)
}
val chooseFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
@@ -158,14 +169,7 @@ class UserAssetActivity : BaseActivity() {
}
private fun importAssetFromQRcode(): Boolean {
RxPermissions(this)
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
scanQRCodeForAssetURL.launch(Intent(this, ScannerActivity::class.java))
else
toast(R.string.toast_permission_denied)
}
requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA)
return true
}
@@ -349,4 +353,4 @@ class UserAssetActivity : BaseActivity() {
class UserAssetViewHolder(val itemUserAssetBinding: ItemRecyclerUserAssetBinding) :
RecyclerView.ViewHolder(itemUserAssetBinding.root)
}
}

View File

@@ -4,36 +4,27 @@ import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import com.v2ray.ang.dto.AppInfo
import io.reactivex.rxjava3.core.Observable
import kotlinx.coroutines.withContext
import kotlinx.coroutines.Dispatchers
object AppManagerUtil {
private fun loadNetworkAppList(ctx: Context): ArrayList<AppInfo> {
val packageManager = ctx.packageManager
val packages = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS)
val apps = ArrayList<AppInfo>()
suspend fun loadNetworkAppList(context: Context): ArrayList<AppInfo> =
withContext(Dispatchers.IO) {
val packageManager = context.packageManager
val packages = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS)
val apps = ArrayList<AppInfo>()
for (pkg in packages) {
val applicationInfo = pkg.applicationInfo ?: continue
for (pkg in packages) {
val applicationInfo = pkg.applicationInfo ?: continue
val appName = applicationInfo.loadLabel(packageManager).toString()
val appIcon = applicationInfo.loadIcon(packageManager) ?: continue
val isSystemApp = (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) > 0
val appName = applicationInfo.loadLabel(packageManager).toString()
val appIcon = applicationInfo.loadIcon(packageManager) ?: continue
val isSystemApp = (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) > 0
val appInfo = AppInfo(appName, pkg.packageName, appIcon, isSystemApp, 0)
apps.add(appInfo)
val appInfo = AppInfo(appName, pkg.packageName, appIcon, isSystemApp, 0)
apps.add(appInfo)
}
return@withContext apps
}
return apps
}
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> =
Observable.unsafeCreate {
it.onNext(loadNetworkAppList(ctx))
}
// val PackageInfo.hasInternetPermission: Boolean
// get() {
// val permissions = requestedPermissions
// return permissions?.any { it == Manifest.permission.INTERNET } ?: false
// }
}
}

View File

@@ -1,6 +1,6 @@
[versions]
agp = "8.8.1"
desugar_jdk_libs = "2.1.4"
agp = "8.8.2"
desugar_jdk_libs = "2.1.5"
gradleLicensePlugin = "0.9.8"
kotlin = "2.1.10"
coreKtx = "1.15.0"
@@ -9,14 +9,13 @@ junitVersion = "1.2.1"
espressoCore = "3.6.1"
appcompat = "1.7.0"
material = "1.12.0"
activity = "1.10.0"
constraintlayout = "2.2.0"
mmkvStatic = "1.3.11"
activity = "1.10.1"
constraintlayout = "2.2.1"
mmkvStatic = "1.3.12"
gson = "2.11.0"
quickieFoss = "1.13.1"
rxjava = "3.1.10"
rxandroid = "3.0.2"
rxpermissions = "0.12"
kotlinx-coroutines-android = "1.10.1"
kotlinx-coroutines-core = "1.10.1"
swiperefreshlayout = "1.1.0"
toastcompat = "1.1.0"
editorkit = "2.9.0"
@@ -43,9 +42,8 @@ androidx-constraintlayout = { group = "androidx.constraintlayout", name = "const
mmkv-static = { module = "com.tencent:mmkv-static", version.ref = "mmkvStatic" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
quickie-foss = { module = "com.github.T8RIN.QuickieExtended:quickie-foss", version.ref = "quickieFoss" }
rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava" }
rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroid" }
rxpermissions = { module = "com.github.tbruyelle:rxpermissions", version.ref = "rxpermissions" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version = "kotlinx-coroutines-android" }
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version = "kotlinx-coroutines-core" }
toastcompat = { module = "me.drakeet.support:toastcompat", version.ref = "toastcompat" }
editorkit = { module = "com.blacksquircle.ui:editorkit", version.ref = "editorkit" }
language-base = { module = "com.blacksquircle.ui:language-base", version.ref = "editorkit" }

View File

@@ -324,6 +324,7 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest
if w.sniffingConfig != nil {
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
content.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded
content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
content.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly
}

View File

@@ -120,7 +120,7 @@ func (w *ServerWorker) handleStatusKeepAlive(meta *FrameMetadata, reader *buf.Bu
func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, reader *buf.BufferedReader) error {
// deep-clone outbounds because it is going to be mutated concurrently
// (Target and OriginalTarget)
ctx = session.ContextCloneOutbounds(ctx)
ctx = session.ContextCloneOutboundsAndContent(ctx)
errors.LogInfo(ctx, "received request for ", meta.Target)
{
msg := &log.AccessMessage{

View File

@@ -63,7 +63,7 @@ func SniffHTTP(b []byte, c context.Context) (*SniffHeader, error) {
ShouldSniffAttr := true
// If content.Attributes have information, that means it comes from HTTP inbound PlainHTTP mode.
// It will set attributes, so skip it.
if content == nil || content.AttributeLen() != 0 {
if content == nil || len(content.Attributes) != 0 {
ShouldSniffAttr = false
}
if err := beginWithHTTPMethod(b); err != nil {

View File

@@ -42,7 +42,7 @@ func ContextWithOutbounds(ctx context.Context, outbounds []*Outbound) context.Co
return context.WithValue(ctx, outboundSessionKey, outbounds)
}
func ContextCloneOutbounds(ctx context.Context) context.Context {
func ContextCloneOutboundsAndContent(ctx context.Context) context.Context {
outbounds := OutboundsFromContext(ctx)
newOutbounds := make([]*Outbound, len(outbounds))
for i, ob := range outbounds {
@@ -55,7 +55,15 @@ func ContextCloneOutbounds(ctx context.Context) context.Context {
newOutbounds[i] = &v
}
return ContextWithOutbounds(ctx, newOutbounds)
content := ContentFromContext(ctx)
newContent := Content{}
if content != nil {
newContent = *content
if content.Attributes != nil {
panic("content.Attributes != nil")
}
}
return ContextWithContent(ContextWithOutbounds(ctx, newOutbounds), &newContent)
}
func OutboundsFromContext(ctx context.Context) []*Outbound {

View File

@@ -4,7 +4,6 @@ package session // import "github.com/xtls/xray-core/common/session"
import (
"context"
"math/rand"
"sync"
c "github.com/xtls/xray-core/common/ctx"
"github.com/xtls/xray-core/common/errors"
@@ -75,8 +74,8 @@ type Outbound struct {
// SniffingRequest controls the behavior of content sniffing.
type SniffingRequest struct {
ExcludeForDomain []string
OverrideDestinationForProtocol []string
ExcludeForDomain []string // read-only once set
OverrideDestinationForProtocol []string // read-only once set
Enabled bool
MetadataOnly bool
RouteOnly bool
@@ -92,10 +91,6 @@ type Content struct {
Attributes map[string]string
SkipDNSResolve bool
mu sync.Mutex
isLocked bool
}
// Sockopt is the settings for socket connection.
@@ -104,22 +99,8 @@ type Sockopt struct {
Mark int32
}
// Some how when using mux, there will be a same ctx between different requests
// This will cause problem as it's designed for single request, like concurrent map writes
// Add a Mutex as a temp solution
// SetAttribute attaches additional string attributes to content.
func (c *Content) SetAttribute(name string, value string) {
if c.isLocked {
errors.LogError(context.Background(), "Multiple goroutines are tring to access one routing content, tring to write ", name, ":", value)
}
c.mu.Lock()
c.isLocked = true
defer func() {
c.isLocked = false
c.mu.Unlock()
}()
if c.Attributes == nil {
c.Attributes = make(map[string]string)
}
@@ -128,24 +109,8 @@ func (c *Content) SetAttribute(name string, value string) {
// Attribute retrieves additional string attributes from content.
func (c *Content) Attribute(name string) string {
c.mu.Lock()
c.isLocked = true
defer func() {
c.isLocked = false
c.mu.Unlock()
}()
if c.Attributes == nil {
return ""
}
return c.Attributes[name]
}
func (c *Content) AttributeLen() int {
c.mu.Lock()
c.isLocked = true
defer func() {
c.isLocked = false
c.mu.Unlock()
}()
return len(c.Attributes)
}

View File

@@ -502,6 +502,7 @@ type REALITYConfig struct {
Fingerprint string `json:"fingerprint"`
ServerName string `json:"serverName"`
Password string `json:"password"`
PublicKey string `json:"publicKey"`
ShortId string `json:"shortId"`
SpiderX string `json:"spiderX"`
@@ -610,11 +611,14 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
if len(c.ServerNames) != 0 {
return nil, errors.New(`non-empty "serverNames", please use "serverName" instead`)
}
if c.Password != "" {
c.PublicKey = c.Password
}
if c.PublicKey == "" {
return nil, errors.New(`empty "publicKey"`)
return nil, errors.New(`empty "password"`)
}
if config.PublicKey, err = base64.RawURLEncoding.DecodeString(c.PublicKey); err != nil || len(config.PublicKey) != 32 {
return nil, errors.New(`invalid "publicKey": `, c.PublicKey)
return nil, errors.New(`invalid "password": `, c.PublicKey)
}
if len(c.ShortIds) != 0 {
return nil, errors.New(`non-empty "shortIds", please use "shortId" instead`)

View File

@@ -2224,6 +2224,7 @@ from .tvplay import (
TVPlayIE,
)
from .tvplayer import TVPlayerIE
from .tvw import TvwIE
from .tweakers import TweakersIE
from .twentymin import TwentyMinutenIE
from .twentythreevideo import TwentyThreeVideoIE

View File

@@ -1,35 +1,36 @@
from .common import InfoExtractor
from ..utils import parse_age_limit, parse_duration, traverse_obj
from ..utils import parse_age_limit, parse_duration, url_or_none
from ..utils.traversal import traverse_obj
class MagellanTVIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?magellantv\.com/(?:watch|video)/(?P<id>[\w-]+)'
_TESTS = [{
'url': 'https://www.magellantv.com/watch/my-dads-on-death-row?type=v',
'url': 'https://www.magellantv.com/watch/incas-the-new-story?type=v',
'info_dict': {
'id': 'my-dads-on-death-row',
'id': 'incas-the-new-story',
'ext': 'mp4',
'title': 'My Dad\'s On Death Row',
'description': 'md5:33ba23b9f0651fc4537ed19b1d5b0d7a',
'duration': 3780.0,
'title': 'Incas: The New Story',
'description': 'md5:936c7f6d711c02dfb9db22a067b586fe',
'age_limit': 14,
'tags': ['Justice', 'Reality', 'United States', 'True Crime'],
'duration': 3060.0,
'tags': ['Ancient History', 'Archaeology', 'Anthropology'],
},
'params': {'skip_download': 'm3u8'},
}, {
'url': 'https://www.magellantv.com/video/james-bulger-the-new-revelations',
'url': 'https://www.magellantv.com/video/tortured-to-death-murdering-the-nanny',
'info_dict': {
'id': 'james-bulger-the-new-revelations',
'id': 'tortured-to-death-murdering-the-nanny',
'ext': 'mp4',
'title': 'James Bulger: The New Revelations',
'description': 'md5:7b97922038bad1d0fe8d0470d8a189f2',
'title': 'Tortured to Death: Murdering the Nanny',
'description': 'md5:d87033594fa218af2b1a8b49f52511e5',
'age_limit': 14,
'duration': 2640.0,
'age_limit': 0,
'tags': ['Investigation', 'True Crime', 'Justice', 'Europe'],
'tags': ['True Crime', 'Murder'],
},
'params': {'skip_download': 'm3u8'},
}, {
'url': 'https://www.magellantv.com/watch/celebration-nation',
'url': 'https://www.magellantv.com/watch/celebration-nation?type=s',
'info_dict': {
'id': 'celebration-nation',
'ext': 'mp4',
@@ -43,10 +44,19 @@ class MagellanTVIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id)
data = traverse_obj(self._search_nextjs_data(webpage, video_id), (
'props', 'pageProps', 'reactContext',
(('video', 'detail'), ('series', 'currentEpisode')), {dict}), get_all=False)
formats, subtitles = self._extract_m3u8_formats_and_subtitles(data['jwpVideoUrl'], video_id)
context = self._search_nextjs_data(webpage, video_id)['props']['pageProps']['reactContext']
data = traverse_obj(context, ((('video', 'detail'), ('series', 'currentEpisode')), {dict}, any))
formats, subtitles = [], {}
for m3u8_url in set(traverse_obj(data, ((('manifests', ..., 'hls'), 'jwp_video_url'), {url_or_none}))):
fmts, subs = self._extract_m3u8_formats_and_subtitles(
m3u8_url, video_id, 'mp4', m3u8_id='hls', fatal=False)
formats.extend(fmts)
self._merge_subtitles(subs, target=subtitles)
if not formats and (error := traverse_obj(context, ('errorDetailPage', 'errorMessage', {str}))):
if 'available in your country' in error:
self.raise_geo_restricted(msg=error)
self.raise_no_formats(f'{self.IE_NAME} said: {error}', expected=True)
return {
'id': video_id,

View File

@@ -4,7 +4,9 @@ from .common import InfoExtractor
from ..utils import (
extract_attributes,
unified_timestamp,
url_or_none,
)
from ..utils.traversal import traverse_obj
class N1InfoAssetIE(InfoExtractor):
@@ -35,9 +37,9 @@ class N1InfoIIE(InfoExtractor):
IE_NAME = 'N1Info:article'
_VALID_URL = r'https?://(?:(?:\w+\.)?n1info\.\w+|nova\.rs)/(?:[^/?#]+/){1,2}(?P<id>[^/?#]+)'
_TESTS = [{
# Youtube embedded
# YouTube embedded
'url': 'https://rs.n1info.com/sport-klub/tenis/kako-je-djokovic-propustio-istorijsku-priliku-video/',
'md5': '01ddb6646d0fd9c4c7d990aa77fe1c5a',
'md5': '987ce6fd72acfecc453281e066b87973',
'info_dict': {
'id': 'L5Hd4hQVUpk',
'ext': 'mp4',
@@ -45,7 +47,26 @@ class N1InfoIIE(InfoExtractor):
'title': 'Ozmo i USO21, ep. 13: Novak Đoković Danil Medvedev | Ključevi Poraza, Budućnost | SPORT KLUB TENIS',
'description': 'md5:467f330af1effedd2e290f10dc31bb8e',
'uploader': 'Sport Klub',
'uploader_id': 'sportklub',
'uploader_id': '@sportklub',
'uploader_url': 'https://www.youtube.com/@sportklub',
'channel': 'Sport Klub',
'channel_id': 'UChpzBje9Ro6CComXe3BgNaw',
'channel_url': 'https://www.youtube.com/channel/UChpzBje9Ro6CComXe3BgNaw',
'channel_is_verified': True,
'channel_follower_count': int,
'comment_count': int,
'view_count': int,
'like_count': int,
'age_limit': 0,
'duration': 1049,
'thumbnail': 'https://i.ytimg.com/vi/L5Hd4hQVUpk/maxresdefault.jpg',
'chapters': 'count:9',
'categories': ['Sports'],
'tags': 'count:10',
'timestamp': 1631522787,
'playable_in_embed': True,
'availability': 'public',
'live_status': 'not_live',
},
}, {
'url': 'https://rs.n1info.com/vesti/djilas-los-plan-za-metro-nece-resiti-nijedan-saobracajni-problem/',
@@ -55,6 +76,7 @@ class N1InfoIIE(InfoExtractor):
'title': 'Đilas: Predlog izgradnje metroa besmislen; SNS odbacuje navode',
'upload_date': '20210924',
'timestamp': 1632481347,
'thumbnail': 'http://n1info.rs/wp-content/themes/ucnewsportal-n1/dist/assets/images/placeholder-image-video.jpg',
},
'params': {
'skip_download': True,
@@ -67,6 +89,7 @@ class N1InfoIIE(InfoExtractor):
'title': 'Zadnji dnevi na kopališču Ilirija: “Ilirija ni umrla, ubili so jo”',
'timestamp': 1632567630,
'upload_date': '20210925',
'thumbnail': 'https://n1info.si/wp-content/uploads/2021/09/06/1630945843-tomaz3.png',
},
'params': {
'skip_download': True,
@@ -81,6 +104,14 @@ class N1InfoIIE(InfoExtractor):
'upload_date': '20210924',
'timestamp': 1632448649.0,
'uploader': 'YouLotWhatDontStop',
'display_id': 'pu9wbx',
'channel_id': 'serbia',
'comment_count': int,
'like_count': int,
'dislike_count': int,
'age_limit': 0,
'duration': 134,
'thumbnail': 'https://external-preview.redd.it/5nmmawSeGx60miQM3Iq-ueC9oyCLTLjjqX-qqY8uRsc.png?format=pjpg&auto=webp&s=2f973400b04d23f871b608b178e47fc01f9b8f1d',
},
'params': {
'skip_download': True,
@@ -93,6 +124,7 @@ class N1InfoIIE(InfoExtractor):
'title': 'Žaklina Tatalović Ani Brnabić: Pričate laži (VIDEO)',
'upload_date': '20211102',
'timestamp': 1635861677,
'thumbnail': 'https://nova.rs/wp-content/uploads/2021/11/02/1635860298-TNJG_Ana_Brnabic_i_Zaklina_Tatalovic_100_dana_Vlade_GP.jpg',
},
}, {
'url': 'https://n1info.rs/vesti/cuta-biti-u-kosovskoj-mitrovici-znaci-da-te-docekaju-eksplozivnim-napravama/',
@@ -104,6 +136,16 @@ class N1InfoIIE(InfoExtractor):
'timestamp': 1687290536,
'thumbnail': 'https://cdn.brid.tv/live/partners/26827/snapshot/1332368_th_6492013a8356f_1687290170.jpg',
},
}, {
'url': 'https://n1info.rs/vesti/vuciceva-turneja-po-srbiji-najavljuje-kontrarevoluciju-preti-svom-narodu-vredja-novinare/',
'info_dict': {
'id': '2025974',
'ext': 'mp4',
'title': 'Vučićeva turneja po Srbiji: Najavljuje kontrarevoluciju, preti svom narodu, vređa novinare',
'thumbnail': 'https://cdn-uc.brid.tv/live/partners/26827/snapshot/2025974_fhd_67c4a23280a81_1740939826.jpg',
'timestamp': 1740939936,
'upload_date': '20250302',
},
}, {
'url': 'https://hr.n1info.com/vijesti/pravobraniteljica-o-ubojstvu-u-zagrebu-radi-se-o-doista-nezapamcenoj-situaciji/',
'only_matching': True,
@@ -115,11 +157,11 @@ class N1InfoIIE(InfoExtractor):
title = self._html_search_regex(r'<h1[^>]+>(.+?)</h1>', webpage, 'title')
timestamp = unified_timestamp(self._html_search_meta('article:published_time', webpage))
plugin_data = self._html_search_meta('BridPlugin', webpage)
plugin_data = re.findall(r'\$bp\("(?:Brid|TargetVideo)_\d+",\s(.+)\);', webpage)
entries = []
if plugin_data:
site_id = self._html_search_regex(r'site:(\d+)', webpage, 'site id')
for video_data in re.findall(r'\$bp\("Brid_\d+", (.+)\);', webpage):
for video_data in plugin_data:
video_id = self._parse_json(video_data, title)['video']
entries.append({
'id': video_id,
@@ -140,7 +182,7 @@ class N1InfoIIE(InfoExtractor):
'url': video_data.get('data-url'),
'id': video_data.get('id'),
'title': title,
'thumbnail': video_data.get('data-thumbnail'),
'thumbnail': traverse_obj(video_data, (('data-thumbnail', 'data-default_thumbnail'), {url_or_none}, any)),
'timestamp': timestamp,
'ie_key': 'N1InfoAsset',
})
@@ -152,7 +194,7 @@ class N1InfoIIE(InfoExtractor):
if url.startswith('https://www.youtube.com'):
entries.append(self.url_result(url, ie='Youtube'))
elif url.startswith('https://www.redditmedia.com'):
entries.append(self.url_result(url, ie='RedditR'))
entries.append(self.url_result(url, ie='Reddit'))
return {
'_type': 'playlist',

Some files were not shown because too many files have changed in this diff Show More