Update On Fri May 23 20:34:58 CEST 2025

This commit is contained in:
github-action[bot]
2025-05-23 20:34:59 +02:00
parent 045e753c5c
commit fb810787a1
63 changed files with 740 additions and 402 deletions

1
.github/update.log vendored
View File

@@ -1007,3 +1007,4 @@ Update On Mon May 19 20:37:08 CEST 2025
Update On Tue May 20 20:37:19 CEST 2025
Update On Wed May 21 20:37:50 CEST 2025
Update On Thu May 22 20:37:12 CEST 2025
Update On Fri May 23 20:34:50 CEST 2025

View File

@@ -84,7 +84,14 @@ func getCache() (*ifaceCache, error) {
continue // interface down
}
for _, prefix := range ipNets {
cache.ifTable.Insert(prefix, ifaceObj)
if _, ok := cache.ifTable.Get(prefix); ok {
// maybe two interfaces have the same prefix but different address,
// so we add a special /32(ipv4) or /128(ipv6) item to let ResolveInterfaceByAddr
// could find the correct interface
cache.ifTable.Insert(netip.PrefixFrom(prefix.Addr(), prefix.Addr().BitLen()), ifaceObj)
} else {
cache.ifTable.Insert(prefix, ifaceObj)
}
}
}

View File

@@ -25,13 +25,13 @@ require (
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639
github.com/metacubex/randv2 v0.2.0
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7
github.com/metacubex/sing v0.5.3
github.com/metacubex/sing-mux v0.3.2
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f
github.com/metacubex/sing-shadowsocks v0.2.9
github.com/metacubex/sing-shadowsocks2 v0.2.3
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6
github.com/metacubex/sing-tun v0.4.6-0.20250523121712-972bc1c2b1e7
github.com/metacubex/sing-vmess v0.2.1
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee

View File

@@ -116,20 +116,20 @@ github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639/go.mod h1:Kc6
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 h1:m4nSxvw46JEgxMzzmnXams+ebwabcry4Ydep/zNiesQ=
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing v0.5.3 h1:QWdN16WFKMk06x4nzkc8SvZ7y2x+TLQrpkPoHs+WSVM=
github.com/metacubex/sing v0.5.3/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a h1:Ho73vGiB94LmtK5T+tKVwtCNEi/YiHmPjlqpHSAmAVs=
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f h1:mP3vIm+9hRFI0C0Vl3pE0NESF/L85FDbuB0tGgUii6I=
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00=
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA=
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0=
github.com/metacubex/sing-shadowsocks2 v0.2.3/go.mod h1:/WNy/Q8ahLCoPRriWuFZFD0Jy+JNp1MEQl28Zw6SaF8=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6 h1:TAwL91XPa6x1QK55CRm+VTzPvLPUfEr/uFDnOZArqEU=
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
github.com/metacubex/sing-tun v0.4.6-0.20250523121712-972bc1c2b1e7 h1:vxkBCkZocH2de2tqeeCZxWvT1VjSJPFkE/8hGqvcLCQ=
github.com/metacubex/sing-tun v0.4.6-0.20250523121712-972bc1c2b1e7/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
github.com/metacubex/sing-vmess v0.2.1 h1:I6gM3VUjtvJ15D805EUbNH+SRBuqzJeFnuIbKYUsWZ0=
github.com/metacubex/sing-vmess v0.2.1/go.mod h1:DsODWItJtOMZNna8Qhheg8r3tUivrcO3vWgaTYKnfTo=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=

View File

@@ -11,7 +11,7 @@
"build": "tsc"
},
"dependencies": {
"@tanstack/react-query": "5.76.1",
"@tanstack/react-query": "5.76.2",
"@tauri-apps/api": "2.5.0",
"ahooks": "3.8.5",
"dayjs": "1.11.13",

View File

@@ -55,12 +55,12 @@
"@csstools/normalize.css": "12.1.1",
"@emotion/babel-plugin": "11.13.5",
"@emotion/react": "11.14.0",
"@iconify/json": "2.2.340",
"@iconify/json": "2.2.341",
"@monaco-editor/react": "4.7.0",
"@tanstack/react-query": "5.76.1",
"@tanstack/react-router": "1.120.5",
"@tanstack/react-router-devtools": "1.120.6",
"@tanstack/router-plugin": "1.120.5",
"@tanstack/react-query": "5.76.2",
"@tanstack/react-router": "1.120.7",
"@tanstack/react-router-devtools": "1.120.7",
"@tanstack/router-plugin": "1.120.7",
"@tauri-apps/plugin-clipboard-manager": "2.2.2",
"@tauri-apps/plugin-dialog": "2.2.1",
"@tauri-apps/plugin-fs": "2.2.1",

View File

@@ -1,11 +1,11 @@
{
"manifest_version": 1,
"latest": {
"mihomo": "v1.19.8",
"mihomo_alpha": "alpha-fd959fe",
"mihomo": "v1.19.9",
"mihomo_alpha": "alpha-b1d12a1",
"clash_rs": "v0.7.8",
"clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.7.8-alpha+sha.dbd16f0"
"clash_rs_alpha": "0.7.8-alpha+sha.5e5a732"
},
"arch_template": {
"mihomo": {
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
}
},
"updated_at": "2025-05-21T22:21:11.677Z"
"updated_at": "2025-05-22T22:21:09.549Z"
}

View File

@@ -173,8 +173,8 @@ importers:
frontend/interface:
dependencies:
'@tanstack/react-query':
specifier: 5.76.1
version: 5.76.1(react@19.1.0)
specifier: 5.76.2
version: 5.76.2(react@19.1.0)
'@tauri-apps/api':
specifier: 2.5.0
version: 2.5.0
@@ -247,7 +247,7 @@ importers:
version: 4.1.7
'@tanstack/router-zod-adapter':
specifier: 1.81.5
version: 1.81.5(@tanstack/react-router@1.120.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(zod@3.24.4)
version: 1.81.5(@tanstack/react-router@1.120.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(zod@3.24.4)
'@tauri-apps/api':
specifier: 2.5.0
version: 2.5.0
@@ -337,23 +337,23 @@ importers:
specifier: 11.14.0
version: 11.14.0(@types/react@19.1.4)(react@19.1.0)
'@iconify/json':
specifier: 2.2.340
version: 2.2.340
specifier: 2.2.341
version: 2.2.341
'@monaco-editor/react':
specifier: 4.7.0
version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tanstack/react-query':
specifier: 5.76.1
version: 5.76.1(react@19.1.0)
specifier: 5.76.2
version: 5.76.2(react@19.1.0)
'@tanstack/react-router':
specifier: 1.120.5
version: 1.120.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
specifier: 1.120.7
version: 1.120.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tanstack/react-router-devtools':
specifier: 1.120.6
version: 1.120.6(@tanstack/react-router@1.120.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.120.5)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)
specifier: 1.120.7
version: 1.120.7(@tanstack/react-router@1.120.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.120.7)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)
'@tanstack/router-plugin':
specifier: 1.120.5
version: 1.120.5(@tanstack/react-router@1.120.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))
specifier: 1.120.7
version: 1.120.7(@tanstack/react-router@1.120.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))
'@tauri-apps/plugin-clipboard-manager':
specifier: 2.2.2
version: 2.2.2
@@ -1666,8 +1666,8 @@ packages:
'@vue/compiler-sfc':
optional: true
'@iconify/json@2.2.340':
resolution: {integrity: sha512-pr5oueoKCYmLce5ukSrKihnLDW0CEuQVPIWtrlw8qgx5RBzSxg/aIsuv6Me35ZXrCkBUKxK8h1XLqCLlMB7L6g==}
'@iconify/json@2.2.341':
resolution: {integrity: sha512-IwH6m5hbVRtjBcmcI7xI6/H3VV3Tt0rqzWO4UxV1L82Cv32CsnvwpykLe9a2DgC06/gnrCmp3vgEzPsEHzndqg==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -2686,24 +2686,24 @@ packages:
resolution: {integrity: sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==}
engines: {node: '>=12'}
'@tanstack/query-core@5.76.0':
resolution: {integrity: sha512-FN375hb8ctzfNAlex5gHI6+WDXTNpe0nbxp/d2YJtnP+IBM6OUm7zcaoCW6T63BawGOYZBbKC0iPvr41TteNVg==}
'@tanstack/query-core@5.76.2':
resolution: {integrity: sha512-PFGwWh5ss9cJQ67l6bZ7hqXbisX2gy13G2jP+VGY1bgdbCfOMWh6UBVnN62QbFXro6CCoX9hYzTnZHr6Rz00YQ==}
'@tanstack/react-query@5.76.1':
resolution: {integrity: sha512-YxdLZVGN4QkT5YT1HKZQWiIlcgauIXEIsMOTSjvyD5wLYK8YVvKZUPAysMqossFJJfDpJW3pFn7WNZuPOqq+fw==}
'@tanstack/react-query@5.76.2':
resolution: {integrity: sha512-rGkWberCrFdIxMdvSAJM/UOKeu0O/JVTbMmfhQoJpiU9Uq0EDx2EMCadnNuJWbXR4smDA2t7DY3NKkYFmDVS5A==}
peerDependencies:
react: ^18 || ^19
'@tanstack/react-router-devtools@1.120.6':
resolution: {integrity: sha512-EKK0+s10S7/00lGh881dDMvP7ySlCTRNKvrHx0wlO9JnGZ+WuNMyNgRRiQdZrSLVZ96se45cTbc4HeImAZN9BA==}
'@tanstack/react-router-devtools@1.120.7':
resolution: {integrity: sha512-0CsgRHYZhFHvHxexBBMAWi88+QFZzQLYVnezO6BrIcL5Y8FmMK2Ww+hSdVbOzUAZZHFPR/LkUduCoUBsYEMQMA==}
engines: {node: '>=12'}
peerDependencies:
'@tanstack/react-router': ^1.120.5
'@tanstack/react-router': ^1.120.7
react: '>=18.0.0 || >=19.0.0'
react-dom: '>=18.0.0 || >=19.0.0'
'@tanstack/react-router@1.120.5':
resolution: {integrity: sha512-A+YRftGwAeFBxa8DF5ujNYqkSEbjCa1KjxDNYr+jWj16jjTxrz/XqgOJCv5ZfbAqqqOa3yLYoQbWa7OGz5jHuA==}
'@tanstack/react-router@1.120.7':
resolution: {integrity: sha512-fKzZXYC0mIY6/deHXvHqUuWrZ9fWOkdXB7IOofLDCBuIyi8eeSAoB6qlUbHnI4adZZVaDVuDqkiXp+1Yp4FBgA==}
engines: {node: '>=12'}
peerDependencies:
react: '>=18.0.0 || >=19.0.0'
@@ -2728,15 +2728,15 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@tanstack/router-core@1.120.5':
resolution: {integrity: sha512-IXLNv3j7rpTL/YNCWHijZgrnxFuvD4Nz/nUiGSak4x5BKzlnuZEso81xFcIuczVrEW72NxZv8IfzpR5M5Tuc0A==}
'@tanstack/router-core@1.120.7':
resolution: {integrity: sha512-CAoRZtDJv5XIn4dBNsg/JqCfG1/ioMJjPR+G71ZhwUgPOBt7gmAGEAwdXD0gwdcBoOupL4//6/NPArNdMJj7yg==}
engines: {node: '>=12'}
'@tanstack/router-devtools-core@1.120.6':
resolution: {integrity: sha512-+E0WjRDTgwLRxH2NN/NCIyRxRCu3/iYFEeegWCvCjKFa1Tm34dulI3mX8YNjfwQVjo++XLjRRtUKjqVVpF1X6Q==}
'@tanstack/router-devtools-core@1.120.7':
resolution: {integrity: sha512-ijBs21+DdLx5llm+wHJgZ/qBF1m4QLq+b2C0/aA54NGvjSdenWPg+TYnjyUK/34LoDYUw+poIal1aSynaVribw==}
engines: {node: '>=12'}
peerDependencies:
'@tanstack/router-core': ^1.120.5
'@tanstack/router-core': ^1.120.7
csstype: ^3.0.10
solid-js: '>=1.9.5'
tiny-invariant: ^1.3.3
@@ -2744,21 +2744,21 @@ packages:
csstype:
optional: true
'@tanstack/router-generator@1.120.5':
resolution: {integrity: sha512-09YWjEXqrJE+j89gKBGSbt0ca3CYwo0QUlsDlCQ1WSI4e3GuQLvSROjq97nMjaPtrnWiROxgnw5S18BlcVVvfA==}
'@tanstack/router-generator@1.120.7':
resolution: {integrity: sha512-vMXzEuNDQBmMwJ96VPzfhxpxm4SkL1Dn/iDjPv/LVXSLbIgZT4szln2qUq5B4VchSFTnVUZdPG9rXdnR/YeRzw==}
engines: {node: '>=12'}
peerDependencies:
'@tanstack/react-router': ^1.120.5
'@tanstack/react-router': ^1.120.7
peerDependenciesMeta:
'@tanstack/react-router':
optional: true
'@tanstack/router-plugin@1.120.5':
resolution: {integrity: sha512-5w6vW7g5LgGZ1IM1D2HPInRmJHqK5jSa2REr7QFwKVDvbVHfzOAcqfAIw1kiPRRqIh/r6YSsrGyMV5MVbrJSCA==}
'@tanstack/router-plugin@1.120.7':
resolution: {integrity: sha512-dk/Td/0vunav1UmViIqtGdnP/6MYAnf78UHgy+o8CwtelxAT6B2l0IQ6YAHlRL5EuWcYGlQ0BT7BFY7eCIUNvA==}
engines: {node: '>=12'}
peerDependencies:
'@rsbuild/core': '>=1.0.2'
'@tanstack/react-router': ^1.120.5
'@tanstack/react-router': ^1.120.7
vite: '>=5.0.0 || >=6.0.0'
vite-plugin-solid: ^2.11.2
webpack: '>=5.92.0'
@@ -9488,7 +9488,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@iconify/json@2.2.340':
'@iconify/json@2.2.341':
dependencies:
'@iconify/types': 2.0.0
pathe: 1.1.2
@@ -10483,17 +10483,17 @@ snapshots:
dependencies:
remove-accents: 0.5.0
'@tanstack/query-core@5.76.0': {}
'@tanstack/query-core@5.76.2': {}
'@tanstack/react-query@5.76.1(react@19.1.0)':
'@tanstack/react-query@5.76.2(react@19.1.0)':
dependencies:
'@tanstack/query-core': 5.76.0
'@tanstack/query-core': 5.76.2
react: 19.1.0
'@tanstack/react-router-devtools@1.120.6(@tanstack/react-router@1.120.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.120.5)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)':
'@tanstack/react-router-devtools@1.120.7(@tanstack/react-router@1.120.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.120.7)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)':
dependencies:
'@tanstack/react-router': 1.120.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tanstack/router-devtools-core': 1.120.6(@tanstack/router-core@1.120.5)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)
'@tanstack/react-router': 1.120.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tanstack/router-devtools-core': 1.120.7(@tanstack/router-core@1.120.7)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
solid-js: 1.9.5
@@ -10502,11 +10502,11 @@ snapshots:
- csstype
- tiny-invariant
'@tanstack/react-router@1.120.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
'@tanstack/react-router@1.120.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@tanstack/history': 1.115.0
'@tanstack/react-store': 0.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tanstack/router-core': 1.120.5
'@tanstack/router-core': 1.120.7
jsesc: 3.1.0
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
@@ -10532,15 +10532,15 @@ snapshots:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
'@tanstack/router-core@1.120.5':
'@tanstack/router-core@1.120.7':
dependencies:
'@tanstack/history': 1.115.0
'@tanstack/store': 0.7.0
tiny-invariant: 1.3.3
'@tanstack/router-devtools-core@1.120.6(@tanstack/router-core@1.120.5)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)':
'@tanstack/router-devtools-core@1.120.7(@tanstack/router-core@1.120.7)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)':
dependencies:
'@tanstack/router-core': 1.120.5
'@tanstack/router-core': 1.120.7
clsx: 2.1.1
goober: 2.1.16(csstype@3.1.3)
solid-js: 1.9.5
@@ -10548,16 +10548,16 @@ snapshots:
optionalDependencies:
csstype: 3.1.3
'@tanstack/router-generator@1.120.5(@tanstack/react-router@1.120.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))':
'@tanstack/router-generator@1.120.7(@tanstack/react-router@1.120.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0))':
dependencies:
'@tanstack/virtual-file-routes': 1.115.0
prettier: 3.5.3
tsx: 4.19.4
zod: 3.24.4
optionalDependencies:
'@tanstack/react-router': 1.120.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tanstack/react-router': 1.120.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tanstack/router-plugin@1.120.5(@tanstack/react-router@1.120.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))':
'@tanstack/router-plugin@1.120.7(@tanstack/react-router@1.120.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))':
dependencies:
'@babel/core': 7.26.10
'@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10)
@@ -10565,8 +10565,8 @@ snapshots:
'@babel/template': 7.27.0
'@babel/traverse': 7.27.0
'@babel/types': 7.27.0
'@tanstack/router-core': 1.120.5
'@tanstack/router-generator': 1.120.5(@tanstack/react-router@1.120.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))
'@tanstack/router-core': 1.120.7
'@tanstack/router-generator': 1.120.7(@tanstack/react-router@1.120.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0))
'@tanstack/router-utils': 1.115.0
'@tanstack/virtual-file-routes': 1.115.0
'@types/babel__core': 7.20.5
@@ -10577,7 +10577,7 @@ snapshots:
unplugin: 2.3.2
zod: 3.24.4
optionalDependencies:
'@tanstack/react-router': 1.120.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tanstack/react-router': 1.120.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)
transitivePeerDependencies:
- supports-color
@@ -10589,9 +10589,9 @@ snapshots:
ansis: 3.12.0
diff: 7.0.0
'@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.120.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(zod@3.24.4)':
'@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.120.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(zod@3.24.4)':
dependencies:
'@tanstack/react-router': 1.120.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tanstack/react-router': 1.120.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
zod: 3.24.4
'@tanstack/store@0.7.0': {}

View File

@@ -52,7 +52,6 @@
- 使用操作系统默认的窗口管理器
- 切换、升级、重启内核的状态管理
- 更精细化控制自动日志清理新增1天选项。
- 简化了随机API端口和密钥重启即可刷新
#### 优化了:
- 系统代理 Bypass 设置
@@ -83,6 +82,8 @@
- 优化端口设置退出和保存机制!
- 强制为 Mihomo 配置补全并覆盖 external-controller-cors 字段,默认不允许跨域和仅本地请求,提升 cors 安全性,升级配置时自动覆盖。
- 优化了 随机API端口和密钥刷新按钮
- 简化了 随机API端口和密钥重启即可刷新
- 增强了 随机API端口和密钥的安全生成
## v2.2.3

View File

@@ -1076,6 +1076,7 @@ dependencies = [
"port_scanner",
"rand 0.8.5",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
"regex",
"reqwest",
"reqwest_dav",

View File

@@ -82,6 +82,7 @@ sha2 = "0.10.9"
hex = "0.4.3"
rand = "0.8.5"
rand_chacha = "0.3.1"
rand_core = "0.6.4"
[target.'cfg(windows)'.dependencies]
runas = "=1.2.0"

View File

@@ -1,6 +1,8 @@
use crate::utils::{dirs, help};
use anyhow::Result;
use rand::{rngs::OsRng, Rng};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
use rand_core::OsRng;
use serde::{Deserialize, Serialize};
use serde_yaml::{Mapping, Value};
use std::{
@@ -61,6 +63,9 @@ impl IClashTemp {
"tauri://localhost",
"http://tauri.localhost",
"http://localhost:3000",
"https://yacd.metacubex.one",
"https://metacubex.github.io",
"https://board.zash.run.place",
]
.into(),
);
@@ -72,21 +77,30 @@ impl IClashTemp {
// 生成随机端口动态端口范围1111-65535
fn generate_random_port() -> u16 {
let mut rng = OsRng;
let seed = Self::generate_seed();
let mut rng = ChaCha8Rng::from_seed(seed);
rng.gen_range(1111..=65535)
}
// 生成32位强密码(包含大小写字母、数字、特殊符号)
// 生成64位强密码(包含大小写字母、数字、特殊符号)
fn generate_secret() -> String {
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;':\",.<>/?`~";
let mut rng = OsRng;
(0..32)
let seed = Self::generate_seed();
let mut rng = ChaCha8Rng::from_seed(seed);
(0..64)
.map(|_| CHARS[rng.gen_range(0..CHARS.len())] as char)
.collect()
}
// 生成加密安全的随机种子
fn generate_seed() -> [u8; 32] {
let mut seed = [0u8; 32];
OsRng.fill(&mut seed as &mut [u8]);
seed
}
fn guard(mut config: Mapping) -> Mapping {
// 生成随机控制器端口和密钥
// 填入随机控制器端口和密钥
let ctrl_port = Self::generate_random_port();
let ctrl_addr = format!("127.0.0.1:{}", ctrl_port);
let secret = Self::generate_secret();
@@ -99,7 +113,7 @@ impl IClashTemp {
let socks_port = Self::guard_socks_port(&config);
let port = Self::guard_port(&config);
// 注随机值
// 注随机值
config.insert("external-controller".into(), Value::String(ctrl_addr));
config.insert("secret".into(), Value::String(secret));
@@ -120,6 +134,9 @@ impl IClashTemp {
"tauri://localhost",
"http://tauri.localhost",
"http://localhost:3000",
"https://yacd.metacubex.one",
"https://metacubex.github.io",
"https://board.zash.run.place",
]
.into(),
);

View File

@@ -4,7 +4,6 @@ import { useVerge } from "@/hooks/use-verge";
import { showNotice } from "@/services/noticeService";
import {
ContentCopy,
RefreshRounded,
} from "@mui/icons-material";
import {
Alert,
@@ -22,23 +21,6 @@ import { useLockFn } from "ahooks";
import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
// 随机端口和密码生成
const generateRandomPort = (): number => {
return Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024;
};
const generateRandomPassword = (length: number = 64): string => {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let password = "";
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * charset.length);
password += charset.charAt(randomIndex);
}
return password;
};
export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
@@ -111,23 +93,6 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
}
});
// 生成随机端口
const handleGeneratePort = useLockFn(async () => {
const port = generateRandomPort();
const host = controller.split(':')[0] || '127.0.0.1';
setController(`${host}:${port}`);
showNotice('success', t("Random port generated"), 1000);
return Promise.resolve();
});
// 生成随机 Secret
const handleGenerateSecret = useLockFn(async () => {
const password = generateRandomPassword();
setSecret(password);
showNotice('success', t("Random secret generated"), 1000);
return Promise.resolve();
});
// 复制到剪贴板
const handleCopyToClipboard = useLockFn(async (text: string, type: string) => {
try {
@@ -172,19 +137,7 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
>
<List>
<ListItem sx={{ padding: "5px 2px", display: "flex", justifyContent: "space-between" }}>
<Box display="flex" alignItems="center" gap={1}>
<ListItemText primary={t("External Controller")} />
<Tooltip title={t("Generate Random Port")}>
<IconButton
size="small"
onClick={handleGeneratePort}
color="primary"
disabled={isSaving || isRestarting}
>
<RefreshRounded fontSize="small" />
</IconButton>
</Tooltip>
</Box>
<ListItemText primary={t("External Controller")} />
<Box display="flex" alignItems="center" gap={1}>
<TextField
autoComplete="new-password"
@@ -193,14 +146,13 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
value={controller}
placeholder="Required"
onChange={(e) => setController(e.target.value)}
disabled={isSaving || isRestarting}
disabled={true}
/>
<Tooltip title={t("Copy to clipboard")}>
<IconButton
size="small"
onClick={() => handleCopyToClipboard(controller, "controller")}
color="primary"
disabled={isSaving || isRestarting}
>
<ContentCopy fontSize="small" />
</IconButton>
@@ -209,19 +161,7 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
</ListItem>
<ListItem sx={{ padding: "5px 2px", display: "flex", justifyContent: "space-between" }}>
<Box display="flex" alignItems="center" gap={1}>
<ListItemText primary={t("Core Secret")} />
<Tooltip title={t("Generate Random Secret")}>
<IconButton
size="small"
onClick={handleGenerateSecret}
color="primary"
disabled={isSaving || isRestarting}
>
<RefreshRounded fontSize="small" />
</IconButton>
</Tooltip>
</Box>
<ListItemText primary={t("Core Secret")} />
<Box display="flex" alignItems="center" gap={1}>
<TextField
autoComplete="new-password"
@@ -229,17 +169,14 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
sx={{ width: 175 }}
value={secret}
placeholder={t("Recommended")}
onChange={(e) =>
setSecret(e.target.value?.replace(/[^\x00-\x7F]/g, ""))
}
disabled={isSaving || isRestarting}
onChange={(e) => setSecret(e.target.value)}
disabled={true}
/>
<Tooltip title={t("Copy to clipboard")}>
<IconButton
size="small"
onClick={() => handleCopyToClipboard(secret, "secret")}
color="primary"
disabled={isSaving || isRestarting}
>
<ContentCopy fontSize="small" />
</IconButton>

View File

@@ -93,6 +93,7 @@ $(curdir)/automake/compile := $(curdir)/autoconf/compile $(curdir)/pkgconf/compi
$(curdir)/b43-tools/compile := $(curdir)/bison/compile
$(curdir)/bc/compile := $(curdir)/bison/compile $(curdir)/libtool/compile
$(curdir)/bison/compile := $(curdir)/flex/compile
$(curdir)/bzip2/compile := $(curdir)/cmake/compile
$(curdir)/cbootimage/compile += $(curdir)/automake/compile
$(curdir)/cmake/compile += $(curdir)/libressl/compile $(curdir)/ninja/compile $(curdir)/expat/compile $(curdir)/xz/compile $(curdir)/zlib/compile $(curdir)/zstd/compile
$(curdir)/dosfstools/compile := $(curdir)/automake/compile

View File

@@ -84,7 +84,14 @@ func getCache() (*ifaceCache, error) {
continue // interface down
}
for _, prefix := range ipNets {
cache.ifTable.Insert(prefix, ifaceObj)
if _, ok := cache.ifTable.Get(prefix); ok {
// maybe two interfaces have the same prefix but different address,
// so we add a special /32(ipv4) or /128(ipv6) item to let ResolveInterfaceByAddr
// could find the correct interface
cache.ifTable.Insert(netip.PrefixFrom(prefix.Addr(), prefix.Addr().BitLen()), ifaceObj)
} else {
cache.ifTable.Insert(prefix, ifaceObj)
}
}
}

View File

@@ -25,13 +25,13 @@ require (
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639
github.com/metacubex/randv2 v0.2.0
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7
github.com/metacubex/sing v0.5.3
github.com/metacubex/sing-mux v0.3.2
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f
github.com/metacubex/sing-shadowsocks v0.2.9
github.com/metacubex/sing-shadowsocks2 v0.2.3
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6
github.com/metacubex/sing-tun v0.4.6-0.20250523121712-972bc1c2b1e7
github.com/metacubex/sing-vmess v0.2.1
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee

View File

@@ -116,20 +116,20 @@ github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639/go.mod h1:Kc6
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 h1:m4nSxvw46JEgxMzzmnXams+ebwabcry4Ydep/zNiesQ=
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing v0.5.3 h1:QWdN16WFKMk06x4nzkc8SvZ7y2x+TLQrpkPoHs+WSVM=
github.com/metacubex/sing v0.5.3/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a h1:Ho73vGiB94LmtK5T+tKVwtCNEi/YiHmPjlqpHSAmAVs=
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f h1:mP3vIm+9hRFI0C0Vl3pE0NESF/L85FDbuB0tGgUii6I=
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00=
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA=
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0=
github.com/metacubex/sing-shadowsocks2 v0.2.3/go.mod h1:/WNy/Q8ahLCoPRriWuFZFD0Jy+JNp1MEQl28Zw6SaF8=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6 h1:TAwL91XPa6x1QK55CRm+VTzPvLPUfEr/uFDnOZArqEU=
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
github.com/metacubex/sing-tun v0.4.6-0.20250523121712-972bc1c2b1e7 h1:vxkBCkZocH2de2tqeeCZxWvT1VjSJPFkE/8hGqvcLCQ=
github.com/metacubex/sing-tun v0.4.6-0.20250523121712-972bc1c2b1e7/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
github.com/metacubex/sing-vmess v0.2.1 h1:I6gM3VUjtvJ15D805EUbNH+SRBuqzJeFnuIbKYUsWZ0=
github.com/metacubex/sing-vmess v0.2.1/go.mod h1:DsODWItJtOMZNna8Qhheg8r3tUivrcO3vWgaTYKnfTo=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=

View File

@@ -2176,7 +2176,7 @@ component("base") {
"process/set_process_title_linux.h",
"system/sys_info_linux.cc",
]
if (!is_cronet_build) {
if (!is_android) {
# These dependencies are not required on Android.
sources += [
"nix/mime_util_xdg.cc",

View File

@@ -390,19 +390,19 @@ class TracedArray;
class TracedDictionary;
class EventContext;
class StaticString {
class BASE_EXPORT StaticString {
public:
template <typename T>
StaticString(T) {}
};
class DynamicString {
class BASE_EXPORT DynamicString {
public:
template <typename T>
explicit DynamicString(T) {}
};
class TracedValue {
class BASE_EXPORT TracedValue {
public:
void WriteInt64(int64_t) && {}
void WriteUInt64(uint64_t) && {}
@@ -417,7 +417,7 @@ class TracedValue {
TracedArray WriteArray() &&;
};
class TracedDictionary {
class BASE_EXPORT TracedDictionary {
public:
TracedValue AddItem(StaticString) { return TracedValue(); }
TracedValue AddItem(DynamicString) { return TracedValue(); }
@@ -433,7 +433,7 @@ class TracedDictionary {
TracedArray AddArray(DynamicString);
};
class TracedArray {
class BASE_EXPORT TracedArray {
public:
TracedValue AppendItem() { return TracedValue(); }
@@ -447,16 +447,16 @@ class TracedArray {
template <class T>
void WriteIntoTracedValue(TracedValue, T&&) {}
struct Track {
struct BASE_EXPORT Track {
explicit Track(uint64_t id) {}
};
struct NamedTrack {
struct BASE_EXPORT NamedTrack {
template <class T>
explicit NamedTrack(T name, uint64_t id = 0, Track parent = Track{0}) {}
};
struct Flow {
struct BASE_EXPORT Flow {
static inline Flow ProcessScoped(uint64_t flow_id) { return Flow(); }
static inline Flow FromPointer(void* ptr) { return Flow(); }
static inline Flow Global(uint64_t flow_id) { return Flow(); }

View File

@@ -8,6 +8,7 @@ mkdir -p "$TMPDIR"
if [ "$1" = debug ]; then
out=out/Debug
flags="
chrome_pgo_phase=0
is_debug=true
is_component_build=true"
else
@@ -16,6 +17,7 @@ else
is_official_build=true
exclude_unwind_tables=true
enable_resource_allowlist_generation=false
chrome_pgo_phase=2
symbol_level=0"
fi
@@ -51,7 +53,6 @@ flags="$flags"'
treat_warnings_as_errors=false
is_cronet_build=true
chrome_pgo_phase=2
enable_base_tracing=false
use_udev=false

View File

@@ -628,7 +628,7 @@ config("compiler") {
# Enable ELF CREL (see crbug.com/357878242) for all platforms that use ELF
# (excluding toolchains that use an older version of LLVM).
if (is_linux && !llvm_android_mainline &&
if (is_linux && !llvm_android_mainline && current_cpu != "arm" &&
current_cpu != "mipsel" && current_cpu != "mips64el" &&
default_toolchain != "//build/toolchain/cros:target") {
cflags += [ "-Wa,--crel,--allow-experimental-crel" ]

View File

@@ -14,6 +14,7 @@
#include "build/build_config.h"
#include "net/base/features.h"
#include "net/base/load_flags.h"
#include "net/base/net_export.h"
#include "net/base/proxy_chain.h"
#include "net/base/proxy_server.h"
#include "net/dns/public/secure_dns_policy.h"
@@ -207,7 +208,7 @@ base::TimeDelta ClientSocketPoolManager::unused_idle_socket_timeout(
return base::Seconds(kPreconnectIntervalSec);
}
int InitSocketHandleForHttpRequest(
int NET_EXPORT InitSocketHandleForHttpRequest(
url::SchemeHostPort endpoint,
int request_load_flags,
RequestPriority request_priority,

View File

@@ -2,8 +2,9 @@
icon: material/alert-decagram
---
#### 1.12.0-beta.16
#### 1.12.0-beta.17
* Update quic-go to v0.52.0
* Fixes and improvements
#### 1.12.0-beta.15

View File

@@ -27,10 +27,10 @@ require (
github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.6
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
github.com/sagernet/quic-go v0.51.0-beta.5
github.com/sagernet/sing v0.6.10-0.20250521033217-30d675ea099b
github.com/sagernet/quic-go v0.52.0-beta.1
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b
github.com/sagernet/sing-mux v0.3.2
github.com/sagernet/sing-quic v0.4.1-0.20250511050139-d459f561c9c3
github.com/sagernet/sing-quic v0.5.0-beta.1
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11

View File

@@ -167,13 +167,17 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/quic-go v0.51.0-beta.5 h1:/mME3sJvQ8k/JKP0oC/9XoWrm0znO7hWXviB5yiipJY=
github.com/sagernet/quic-go v0.51.0-beta.5/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.6.10-0.20250521033217-30d675ea099b h1:E9zgBma90grCIC1Rber6UgGw3CzbpeizJdavNu1Fy8M=
github.com/sagernet/sing v0.6.10-0.20250521033217-30d675ea099b/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b h1:ZjTCYPb5f7aHdf1UpUvE22dVmf7BL8eQ/zLZhjgh7Wo=
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE=
github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
github.com/sagernet/sing-quic v0.4.1-0.20250511050139-d459f561c9c3 h1:1J+s1yyZ8+YAYaClI+az8YuFgV9NGXUUCZnriKmos6w=
github.com/sagernet/sing-quic v0.4.1-0.20250511050139-d459f561c9c3/go.mod h1:Mv7CdSyLepmqoLT8rd88Qn3QMv5AbsgjEm3DvEhDVNE=
github.com/sagernet/sing-quic v0.5.0-beta.1 h1:nC0i/s8LhlZB8ev6laZCXF/uiwAE4kRdT4PcDdE4rI4=
github.com/sagernet/sing-quic v0.5.0-beta.1/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=

View File

@@ -3,7 +3,6 @@ package route
import (
"context"
"errors"
"fmt"
"net"
"net/netip"
"os"
@@ -314,14 +313,10 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func {
return nil
}
bindFunc := control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) {
for _, iif := range r.interfaceFinder.Interfaces() {
r.logger.Warn("iif ", iif.Name, ": ", fmt.Sprint(iif.Addresses))
}
remoteAddr := M.ParseSocksaddr(address).Addr
if remoteAddr.IsValid() {
iif, err := r.interfaceFinder.ByAddr(remoteAddr)
if err == nil {
r.logger.Warn("bind to interface ", iif.Name, " (", iif.Index, "): ", remoteAddr)
return iif.Name, iif.Index, nil
}
}
@@ -329,7 +324,6 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func {
if defaultInterface == nil {
return "", -1, tun.ErrNoRoute
}
r.logger.Warn("bind to default interface ", defaultInterface.Name, " (", defaultInterface.Index, "): ", remoteAddr)
return defaultInterface.Name, defaultInterface.Index, nil
})
return func(network, address string, conn syscall.RawConn) error {

View File

@@ -598,7 +598,7 @@ return view.extend({
so = ss.option(form.DynamicList, 'external_controller_cors_allow_origins', _('CORS Allow origins'),
_('CORS allowed origins, <code>*</code> will be used if empty.'));
so.placeholder = 'https://yacd.metacubex.one';
so.placeholder = 'https://board.zash.run.place';
so = ss.option(form.Flag, 'external_controller_cors_allow_private_network', _('CORS Allow private network'),
_('Allow access from private network.</br>' +
@@ -680,6 +680,9 @@ return view.extend({
o = s.taboption('experimental', form.SectionValue, '_experimental', form.NamedSection, 'experimental', 'fchomo', null);
ss = o.subsection;
so = ss.option(form.Flag, 'skip_safe_path_check', _('Disable safe path check'));
so.default = so.disabled;
so = ss.option(form.Flag, 'quic_go_disable_gso', _('Disable GSO of quic-go'));
so.default = so.disabled;

View File

@@ -539,6 +539,10 @@ msgstr ""
msgid "Disable UDP"
msgstr ""
#: htdocs/luci-static/resources/view/fchomo/global.js:683
msgid "Disable safe path check"
msgstr ""
#: htdocs/luci-static/resources/view/fchomo/client.js:750
msgid ""
"Do not resolve the domain connection to IP for this match.</br>Only works "

View File

@@ -547,6 +547,10 @@ msgstr "禁用 SNI"
msgid "Disable UDP"
msgstr "禁用 UDP"
#: htdocs/luci-static/resources/view/fchomo/global.js:683
msgid "Disable safe path check"
msgstr "禁用安全路径检查"
#: htdocs/luci-static/resources/view/fchomo/client.js:750
msgid ""
"Do not resolve the domain connection to IP for this match.</br>Only works "

View File

@@ -547,6 +547,10 @@ msgstr "停用 SNI"
msgid "Disable UDP"
msgstr "停用 UDP"
#: htdocs/luci-static/resources/view/fchomo/global.js:683
msgid "Disable safe path check"
msgstr "禁用安全路徑檢查"
#: htdocs/luci-static/resources/view/fchomo/client.js:750
msgid ""
"Do not resolve the domain connection to IP for this match.</br>Only works "

View File

@@ -110,11 +110,13 @@ start_service() {
mkdir -p "$RUN_DIR"
# Global ENV variables
config_get_bool SKIP_SAFE_PATH_CHECK "experimental" "skip_safe_path_check" "0"
export SKIP_SAFE_PATH_CHECK=$([ "$SKIP_SAFE_PATH_CHECK" = "1" ] && echo true || echo false)
# Client
if [ "$client_enabled" = "1" ]; then
if [ -z "$1" -o "$1" = "mihomo-c" ]; then
# Env variables
export SAFE_PATHS="$RUN_DIR/"
# Generate/Validate client config
ucode -S "$SDL_DIR/generate_client.uc" 2>>"$LOG_PATH" | yq -Poy | yq \
@@ -131,9 +133,20 @@ start_service() {
if [ ! -e "$RUN_DIR/mihomo-c.yaml" ]; then
log "Error: failed to generate client configuration."
return 1
elif ! "$PROG" -t -d "$HM_DIR" -f "$RUN_DIR/mihomo-c.yaml" >/dev/null 2>>"$LOG_PATH"; then
log "Error: wrong client configuration detected."
return 1
else
# Set ENV variables for Client
export SAFE_PATHS="$RUN_DIR$(
yq 'with(.tls; .[] |= sub("(/[^/]+$)", ""))
| [.external-ui, .tls.certificate, .tls.private-key] | unique
| .[] | sub("(^)", ":")' \
"$RUN_DIR/mihomo-c.yaml" | tr -d '\n'
)"
if ! "$PROG" -t -d "$HM_DIR" -f "$RUN_DIR/mihomo-c.yaml" >/dev/null; then
log "Error: wrong client configuration detected."
"$PROG" -t -d "$HM_DIR" -f "$RUN_DIR/mihomo-c.yaml" >>"$LOG_PATH"
return 1
fi
fi
echo > "$RUN_DIR/mihomo-c.log"
@@ -251,7 +264,7 @@ start_service() {
procd_set_param command /bin/sh
procd_append_param command -c "'$PROG' -d '$HM_DIR' -f '$RUN_DIR/mihomo-c.yaml' >> '$RUN_DIR/mihomo-c.log' 2>&1"
procd_set_param env SAFE_PATHS="$RUN_DIR:/etc/ssl" SKIP_SAFE_PATH_CHECK=true # The syntax of this environment variable is the same as the PATH environment variable parsing rules of this operating system (i.e., semicolon-separated under Windows and colon-separated under other systems)
procd_set_param env SAFE_PATHS="$SAFE_PATHS" SKIP_SAFE_PATH_CHECK="$SKIP_SAFE_PATH_CHECK" # The syntax of this environment variable is the same as the PATH environment variable parsing rules of this operating system (i.e., semicolon-separated under Windows and colon-separated under other systems)
# Only supports `Global`` and does not support `Proxy Group` and `Proxy Node`
local bind_ifname
@@ -288,9 +301,20 @@ start_service() {
if [ ! -e "$RUN_DIR/mihomo-s.yaml" ]; then
log "Error: failed to generate server configuration."
return 1
elif ! "$PROG" -t -d "$HM_DIR" -f "$RUN_DIR/mihomo-s.yaml" >/dev/null 2>>"$LOG_PATH"; then
log "Error: wrong server configuration detected."
return 1
else
# Set ENV variables for Server
export SAFE_PATHS="$RUN_DIR$(
yq '[.listeners[] | select(.certificate // .private-key) | [.certificate, .private-key][]]
| .[] |= sub("(/[^/]+$)", "") | unique
| .[] | sub("(^)", ":")' \
"$RUN_DIR/mihomo-s.yaml" | tr -d '\n'
)"
if ! "$PROG" -t -d "$HM_DIR" -f "$RUN_DIR/mihomo-s.yaml" >/dev/null; then
log "Error: wrong server configuration detected."
"$PROG" -t -d "$HM_DIR" -f "$RUN_DIR/mihomo-s.yaml" >>"$LOG_PATH"
return 1
fi
fi
echo > "$RUN_DIR/mihomo-s.log"
@@ -299,6 +323,7 @@ start_service() {
procd_set_param command /bin/sh
procd_append_param command -c "'$PROG' -d '$HM_DIR' -f '$RUN_DIR/mihomo-s.yaml' >> '$RUN_DIR/mihomo-s.log' 2>&1"
procd_set_param env SAFE_PATHS="$SAFE_PATHS" SKIP_SAFE_PATH_CHECK="$SKIP_SAFE_PATH_CHECK" # The syntax of this environment variable is the same as the PATH environment variable parsing rules of this operating system (i.e., semicolon-separated under Windows and colon-separated under other systems)
#procd_set_param capabilities "/etc/capabilities/fchomo.json"
#procd_set_param user mihomo

View File

@@ -38,7 +38,7 @@ LUCI_PKGARCH:=all
LUCI_DEPENDS:= \
+coreutils +coreutils-base64 +dns2tcp +dnsmasq-full +@PACKAGE_dnsmasq_full_ipset +ipset +kmod-ipt-nat +jq \
+ip-full +iptables +iptables-mod-tproxy +lua +lua-neturl +libuci-lua +microsocks \
+tcping +resolveip +shadowsocksr-libev-ssr-check +wget-ssl \
+tcping +resolveip +shadowsocksr-libev-ssr-check +curl \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:curl \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:v2ray-core \
+PACKAGE_$(PKG_NAME)_INCLUDE_Xray:curl \

View File

@@ -95,7 +95,7 @@ get_host_ip() {
if [ -z "$(echo $host | grep -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}")" ]; then
if [ "$host" == "${host#*:[0-9a-fA-F]}" ]; then
ip=$(resolveip -4 -t 3 $host | awk 'NR==1{print}')
[ -z "$ip" ] && ip=$(wget -q -O- http://119.29.29.29/d?dn=$host | awk -F ';' '{print $1}')
[ -z "$ip" ] && ip=$(curl -sSL "http://119.29.29.29/d?dn=$host" | awk -F ';' '{print $1}')
fi
fi
[ -z "$ip" ] || uci_set_by_name $1 ip $ip

View File

@@ -419,6 +419,7 @@ local function processData(szType, content)
elseif szType == "sip008" then
result.type = v2_ss
result.v2ray_protocol = (v2_ss == "v2ray") and "shadowsocks" or nil
result.has_ss_type = has_ss_type
result.server = content.server
result.server_port = content.server_port
result.password = content.password
@@ -432,6 +433,7 @@ local function processData(szType, content)
elseif szType == "ssd" then
result.type = v2_ss
result.v2ray_protocol = (v2_ss == "v2ray") and "shadowsocks" or nil
result.has_ss_type = has_ss_type
result.server = content.server
result.server_port = content.port
result.password = content.password
@@ -680,12 +682,12 @@ local function processData(szType, content)
result.switch_enable = switch_enable
return result
end
-- wget
local function wget(url)
-- curl
local function curl(url)
-- 清理URL中的隐藏字符
url = url:gsub("%s+$", ""):gsub("^%s+", ""):gsub("%z", "")
local stdout = luci.sys.exec('wget-ssl --timeout=20 --tries=3 -q --user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36" --no-check-certificate -O- "' .. url .. '"')
local stdout = luci.sys.exec('curl -sSL --connect-timeout 20 --max-time 30 --retry 3 -A "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36" --insecure --location "' .. url .. '"')
return trim(stdout)
end
@@ -739,7 +741,7 @@ local execute = function()
luci.sys.init.stop(name)
end
for k, url in ipairs(subscribe_url) do
local raw = wget(url)
local raw = curl(url)
if #raw > 0 then
local nodes, szType
local groupHash = md5(url)

View File

@@ -150,7 +150,7 @@ end
local function update(url, file, type, file2)
local Num = 1
local refresh_cmd = "wget --no-check-certificate -q -O /tmp/ssr-update." .. type .. " " .. url
local refresh_cmd = "curl -sSL --insecure -o /tmp/ssr-update." .. type .. " " .. url
local sret = luci.sys.call(refresh_cmd)
if sret == 0 then
if type == "gfw_data" then

View File

@@ -21,22 +21,22 @@ define Download/geoip
HASH:=8023379316bca4713dcfa5ba4ea2fe7f4c127fff64a0cb7859d4756142b2c4dc
endef
GEOSITE_VER:=20250521095120
GEOSITE_VER:=20250523074055
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
define Download/geosite
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
URL_FILE:=dlc.dat
FILE:=$(GEOSITE_FILE)
HASH:=54049654ec3ded40d0ba8e6c00b7f2efb908275b57cd6b0d8ed78b9f5a639105
HASH:=02479c2a08974893a97dfac39c33e32e72b5181ffb669b13ce351de9195e9c47
endef
GEOSITE_IRAN_VER:=202505120041
GEOSITE_IRAN_VER:=202505230820
GEOSITE_IRAN_FILE:=iran.dat.$(GEOSITE_IRAN_VER)
define Download/geosite-ir
URL:=https://github.com/bootmortis/iran-hosted-domains/releases/download/$(GEOSITE_IRAN_VER)/
URL_FILE:=iran.dat
FILE:=$(GEOSITE_IRAN_FILE)
HASH:=1797aec3ef7cf7f9a34fef2e056fd4faa04458bb468b0a6ff4e8b2440bb6ed29
HASH:=063852d8eb2df6a4541bdbaf38b422b8cfadbb6315cc2098f0eb6c9437bbb8b6
endef
define Package/v2ray-geodata/template

View File

@@ -3,7 +3,7 @@
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
[![API](https://img.shields.io/badge/API-21%2B-yellow.svg?style=flat)](https://developer.android.com/about/versions/lollipop)
[![Kotlin Version](https://img.shields.io/badge/Kotlin-2.1.20-blue.svg)](https://kotlinlang.org)
[![Kotlin Version](https://img.shields.io/badge/Kotlin-2.1.21-blue.svg)](https://kotlinlang.org)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayNG)](https://github.com/2dust/v2rayNG/commits/master)
[![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayng/badge)](https://www.codefactor.io/repository/github/2dust/v2rayng)
[![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayNG/latest/total?logo=github)](https://github.com/2dust/v2rayNG/releases)

View File

@@ -144,6 +144,9 @@
<data android:host="install-sub" />
</intent-filter>
</activity>
<activity
android:name=".ui.CheckUpdateActivity"
android:exported="false" />
<activity
android:name=".ui.AboutActivity"
android:exported="false" />

View File

@@ -5,28 +5,21 @@ import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.lifecycle.lifecycleScope
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.dto.CheckUpdateResult
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.toastError
import com.v2ray.ang.extension.toastSuccess
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SpeedtestManager
import com.v2ray.ang.handler.UpdateCheckerManager
import com.v2ray.ang.util.AppManagerUtil
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.ZipUtil
import kotlinx.coroutines.launch
import java.io.File
import java.text.SimpleDateFormat
import java.util.Locale
@@ -105,23 +98,6 @@ class AboutActivity : BaseActivity() {
}
}
//If it is the Google Play version, not be displayed within 1 days after update
// if (Utils.isGoogleFlavor()) {
// val lastUpdateTime = AppManagerUtil.getLastUpdateTime(this)
// val currentTime = System.currentTimeMillis()
// if ((currentTime - lastUpdateTime) < 1 * 24 * 60 * 60 * 1000L) {
// binding.layoutCheckUpdate.visibility = View.GONE
// }
// }
binding.layoutCheckUpdate.setOnClickListener {
checkForUpdates(binding.checkPreRelease.isChecked)
}
binding.checkPreRelease.setOnCheckedChangeListener { _, isChecked ->
MmkvManager.encodeSettings(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, isChecked)
}
binding.checkPreRelease.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, false)
binding.layoutSoureCcode.setOnClickListener {
Utils.openUri(this, AppConfig.APP_URL)
}
@@ -222,28 +198,4 @@ class AboutActivity : BaseActivity() {
}
}
}
private fun checkForUpdates(includePreRelease: Boolean) {
lifecycleScope.launch {
val result = UpdateCheckerManager.checkForUpdate(includePreRelease)
if (result.hasUpdate) {
showUpdateDialog(result)
} else {
toast(R.string.update_already_latest_version)
}
}
}
private fun showUpdateDialog(result: CheckUpdateResult) {
AlertDialog.Builder(this)
.setTitle(getString(R.string.update_new_version_found, result.latestVersion))
.setMessage(result.releaseNotes)
.setPositiveButton(R.string.update_now) { _, _ ->
result.downloadUrl?.let {
Utils.openUri(this, it)
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
}

View File

@@ -0,0 +1,70 @@
package com.v2ray.ang.ui
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityCheckUpdateBinding
import com.v2ray.ang.dto.CheckUpdateResult
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.toastSuccess
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SpeedtestManager
import com.v2ray.ang.handler.UpdateCheckerManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.launch
class CheckUpdateActivity : BaseActivity() {
private val binding by lazy { ActivityCheckUpdateBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
title = getString(R.string.update_check_for_update)
binding.layoutCheckUpdate.setOnClickListener {
checkForUpdates(binding.checkPreRelease.isChecked)
}
binding.checkPreRelease.setOnCheckedChangeListener { _, isChecked ->
MmkvManager.encodeSettings(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, isChecked)
}
binding.checkPreRelease.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, false)
"v${BuildConfig.VERSION_NAME} (${SpeedtestManager.getLibVersion()})".also {
binding.tvVersion.text = it
}
checkForUpdates(binding.checkPreRelease.isChecked)
}
private fun checkForUpdates(includePreRelease: Boolean) {
toast(R.string.update_checking_for_update)
lifecycleScope.launch {
val result = UpdateCheckerManager.checkForUpdate(includePreRelease)
if (result.hasUpdate) {
showUpdateDialog(result)
} else {
toastSuccess(R.string.update_already_latest_version)
}
}
}
private fun showUpdateDialog(result: CheckUpdateResult) {
AlertDialog.Builder(this)
.setTitle(getString(R.string.update_new_version_found, result.latestVersion))
.setMessage(result.releaseNotes)
.setPositiveButton(R.string.update_now) { _, _ ->
result.downloadUrl?.let {
Utils.openUri(this, it)
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
}

View File

@@ -685,6 +685,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
R.id.promotion -> Utils.openUri(this, "${Utils.decode(AppConfig.APP_PROMOTION_URL)}?t=${System.currentTimeMillis()}")
R.id.logcat -> startActivity(Intent(this, LogcatActivity::class.java))
R.id.check_for_update -> startActivity(Intent(this, CheckUpdateActivity::class.java))
R.id.about -> startActivity(Intent(this, AboutActivity::class.java))
}

View File

@@ -6,6 +6,7 @@ import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivitySubEditBinding
import com.v2ray.ang.dto.SubscriptionItem
@@ -109,19 +110,28 @@ class SubEditActivity : BaseActivity() {
*/
private fun deleteServer(): Boolean {
if (editSubId.isNotEmpty()) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
lifecycleScope.launch(Dispatchers.IO) {
MmkvManager.removeSubscription(editSubId)
launch(Dispatchers.Main) {
finish()
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
lifecycleScope.launch(Dispatchers.IO) {
MmkvManager.removeSubscription(editSubId)
launch(Dispatchers.Main) {
finish()
}
}
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
// do nothing
}
.show()
} else {
lifecycleScope.launch(Dispatchers.IO) {
MmkvManager.removeSubscription(editSubId)
launch(Dispatchers.Main) {
finish()
}
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
// do nothing
}
.show()
}
}
return true
}

View File

@@ -8,6 +8,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
@@ -20,6 +21,8 @@ import com.v2ray.ang.helper.ItemTouchHelperAdapter
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
import com.v2ray.ang.util.QRCodeDecoder
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>(), ItemTouchHelperAdapter {
@@ -46,6 +49,10 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
)
}
holder.itemSubSettingBinding.layoutRemove.setOnClickListener {
removeSubscription(subId, position)
}
holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked ->
if (!it.isPressed) return@setOnCheckedChangeListener
subItem.enabled = isChecked
@@ -54,9 +61,11 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
}
if (TextUtils.isEmpty(subItem.url)) {
holder.itemSubSettingBinding.layoutUrl.visibility = View.GONE
holder.itemSubSettingBinding.layoutShare.visibility = View.INVISIBLE
holder.itemSubSettingBinding.chkEnable.visibility = View.INVISIBLE
} else {
holder.itemSubSettingBinding.layoutUrl.visibility = View.VISIBLE
holder.itemSubSettingBinding.layoutShare.visibility = View.VISIBLE
holder.itemSubSettingBinding.chkEnable.visibility = View.VISIBLE
holder.itemSubSettingBinding.layoutShare.setOnClickListener {
@@ -90,6 +99,32 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
}
}
private fun removeSubscription(subId: String, position: Int) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
AlertDialog.Builder(mActivity).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
removeSubscriptionSub(subId, position)
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
//do noting
}
.show()
} else {
removeSubscriptionSub(subId, position)
}
}
private fun removeSubscriptionSub(subId: String, position: Int) {
mActivity.lifecycleScope.launch(Dispatchers.IO) {
MmkvManager.removeSubscription(subId)
launch(Dispatchers.Main) {
notifyItemRemoved(position)
notifyItemRangeChanged(position, mActivity.subscriptions.size)
mActivity.refreshData()
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
return MainViewHolder(
ItemRecyclerSubSettingBinding.inflate(

View File

@@ -111,49 +111,6 @@
android:orientation="vertical"
android:paddingTop="@dimen/padding_spacing_dp16">
<LinearLayout
android:id="@+id/layout_check_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center|start"
android:orientation="horizontal"
android:padding="@dimen/padding_spacing_dp16">
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_check_update_24dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="@dimen/padding_spacing_dp16">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/update_check_for_update"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/check_pre_release"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp16"
android:maxLines="1"
android:text="@string/update_check_pre_release"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorAccent"
app:theme="@style/BrandedSwitch" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_soure_ccode"
android:layout_width="match_parent"

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center|start"
android:orientation="horizontal"
android:padding="@dimen/padding_spacing_dp16">
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_source_code_24dp" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/check_pre_release"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:paddingStart="@dimen/padding_spacing_dp16"
android:text="@string/update_check_pre_release"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorAccent"
app:theme="@style/BrandedSwitch" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_check_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center|start"
android:orientation="horizontal"
android:padding="@dimen/padding_spacing_dp16">
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_check_update_24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="@dimen/padding_spacing_dp16"
android:text="@string/update_check_for_update"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:padding="@dimen/padding_spacing_dp16">
<TextView
android:id="@+id/tv_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_about"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -15,97 +15,144 @@
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:nextFocusRight="@+id/layout_edit"
android:nextFocusRight="@+id/layout_share"
android:orientation="horizontal"
android:padding="@dimen/padding_spacing_dp8">
<LinearLayout
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<TextView
android:id="@+id/tv_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp8"
android:lines="2"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8">
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/layout_share"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:layout_weight="1"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8">
android:paddingStart="@dimen/padding_spacing_dp8">
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_share_24dp" />
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:minLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:nextFocusLeft="@+id/info_container"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8">
android:orientation="horizontal">
<LinearLayout
android:id="@+id/layout_share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:nextFocusLeft="@+id/info_container"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8">
<ImageView
android:layout_width="wrap_content"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_share_24dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8">
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_edit_24dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_remove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8">
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_delete_24dp" />
</LinearLayout>
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_edit_24dp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:id="@+id/layout_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp8"
android:orientation="horizontal">
android:orientation="horizontal"
android:paddingStart="@dimen/padding_spacing_dp8"
android:paddingEnd="@dimen/padding_spacing_dp8">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/chk_enable"
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:paddingTop="@dimen/padding_spacing_dp8">
<TextView
android:id="@+id/tv_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lines="2"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:theme="@style/BrandedSwitch" />
android:layout_marginTop="@dimen/padding_spacing_dp8"
android:orientation="horizontal">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/chk_enable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:theme="@style/BrandedSwitch" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -35,6 +35,10 @@
android:id="@+id/logcat"
android:icon="@drawable/ic_logcat_24dp"
android:title="@string/title_logcat" />
<item
android:id="@+id/check_for_update"
android:icon="@drawable/ic_check_update_24dp"
android:title="@string/update_check_for_update" />
<item
android:id="@+id/about"
android:icon="@drawable/ic_about_24dp"

View File

@@ -316,6 +316,7 @@
<string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</string>
<string name="update_check_pre_release">Check Pre-release</string>
<string name="update_checking_for_update">Checking for update…</string>
<string-array name="share_method">
<item>رمز استجابة سريعة (QRcode)</item>

View File

@@ -315,6 +315,7 @@
<string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</string>
<string name="update_check_pre_release">Check Pre-release</string>
<string name="update_checking_for_update">Checking for update…</string>
<string-array name="share_method">
<item>QR কোড</item>

View File

@@ -324,6 +324,7 @@
<string name="update_new_version_found">نوسخه نۊ ن جوست: %s</string>
<string name="update_now">سکو ورۊ رسۊوی کۊنین</string>
<string name="update_check_pre_release">واجۊری نوسخه یل پؽش ز تیجنیڌن</string>
<string name="update_checking_for_update">Checking for update…</string>
<string-array name="share_method">
<item>QRcode</item>

View File

@@ -321,6 +321,7 @@
<string name="update_new_version_found">نسخه جدید پیدا شد: %s</string>
<string name="update_now">اکنون به روز رسانی کنید</string>
<string name="update_check_pre_release">بررسی نسخه پیش از انتشار</string>
<string name="update_checking_for_update">Checking for update…</string>
<string-array name="share_method">
<item>QRcode</item>

View File

@@ -323,6 +323,7 @@
<string name="update_new_version_found">Найдена новая версия: %s</string>
<string name="update_now">Обновить</string>
<string name="update_check_pre_release">Искать предварительный выпуск</string>
<string name="update_checking_for_update">Checking for update…</string>
<string-array name="share_method">
<item>QR-код</item>

View File

@@ -317,6 +317,7 @@
<string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</string>
<string name="update_check_pre_release">Check Pre-release</string>
<string name="update_checking_for_update">Checking for update…</string>
<string-array name="share_method">
<item>Xuất ra mã QR (Chụp màn hình để lưu)</item>

View File

@@ -315,6 +315,7 @@
<string name="update_new_version_found">发现新版本: %s</string>
<string name="update_now">立即更新</string>
<string name="update_check_pre_release">检查 Pre-release</string>
<string name="update_checking_for_update">正在检查更新中…</string>
<string-array name="share_method">
<item>二维码</item>

View File

@@ -315,6 +315,7 @@
<string name="update_new_version_found">發現新版本: %s</string>
<string name="update_now">立即更新</string>
<string name="update_check_pre_release">檢查 Pre-release</string>
<string name="update_checking_for_update">正在檢查更新中…</string>
<string-array name="share_method">
<item>QR Code</item>

View File

@@ -325,6 +325,7 @@
<string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</string>
<string name="update_check_pre_release">Check Pre-release</string>
<string name="update_checking_for_update">Checking for update…</string>
<string-array name="share_method">
<item>QRcode</item>

View File

@@ -2,7 +2,7 @@
agp = "8.9.3"
desugarJdkLibs = "2.1.5"
gradleLicensePlugin = "0.9.8"
kotlin = "2.1.20"
kotlin = "2.1.21"
coreKtx = "1.16.0"
junit = "4.13.2"
junitVersion = "1.2.1"

View File

@@ -2147,6 +2147,7 @@ from .toggle import (
from .toggo import ToggoIE
from .tonline import TOnlineIE
from .toongoggles import ToonGogglesIE
from .toutiao import ToutiaoIE
from .toutv import TouTvIE
from .toypics import (
ToypicsIE,

View File

@@ -340,8 +340,9 @@ class PatreonIE(PatreonBaseIE):
'channel_follower_count': ('attributes', 'patron_count', {int_or_none}),
}))
# all-lowercase 'referer' so we can smuggle it to Generic, SproutVideo, Vimeo
headers = {'referer': 'https://patreon.com/'}
# Must be all-lowercase 'referer' so we can smuggle it to Generic, SproutVideo, and Vimeo.
# patreon.com URLs redirect to www.patreon.com; this matters when requesting mux.com m3u8s
headers = {'referer': 'https://www.patreon.com/'}
# handle Vimeo embeds
if traverse_obj(attributes, ('embed', 'provider')) == 'Vimeo':
@@ -352,7 +353,7 @@ class PatreonIE(PatreonBaseIE):
v_url, video_id, 'Checking Vimeo embed URL', headers=headers,
fatal=False, errnote=False, expected_status=429): # 429 is TLS fingerprint rejection
entries.append(self.url_result(
VimeoIE._smuggle_referrer(v_url, 'https://patreon.com/'),
VimeoIE._smuggle_referrer(v_url, headers['referer']),
VimeoIE, url_transparent=True))
embed_url = traverse_obj(attributes, ('embed', 'url', {url_or_none}))
@@ -379,11 +380,13 @@ class PatreonIE(PatreonBaseIE):
'url': post_file['url'],
})
elif name == 'video' or determine_ext(post_file.get('url')) == 'm3u8':
formats, subtitles = self._extract_m3u8_formats_and_subtitles(post_file['url'], video_id)
formats, subtitles = self._extract_m3u8_formats_and_subtitles(
post_file['url'], video_id, headers=headers)
entries.append({
'id': video_id,
'formats': formats,
'subtitles': subtitles,
'http_headers': headers,
})
can_view_post = traverse_obj(attributes, 'current_user_can_view')

View File

@@ -5,11 +5,13 @@ from .common import InfoExtractor
from ..utils import (
OnDemandPagedList,
float_or_none,
int_or_none,
orderedSet,
str_or_none,
str_to_int,
traverse_obj,
unified_timestamp,
url_or_none,
)
from ..utils.traversal import require, traverse_obj
class PodchaserIE(InfoExtractor):
@@ -21,24 +23,25 @@ class PodchaserIE(InfoExtractor):
'id': '104365585',
'title': 'Ep. 285 freeze me off',
'description': 'cam ahn',
'thumbnail': r're:^https?://.*\.jpg$',
'thumbnail': r're:https?://.+/.+\.jpg',
'ext': 'mp3',
'categories': ['Comedy'],
'categories': ['Comedy', 'News', 'Politics', 'Arts'],
'tags': ['comedy', 'dark humor'],
'series': 'Cum Town',
'series': 'The Adam Friedland Show Podcast',
'duration': 3708,
'timestamp': 1636531259,
'upload_date': '20211110',
'average_rating': 4.0,
'series_id': '36924',
},
}, {
'url': 'https://www.podchaser.com/podcasts/the-bone-zone-28853',
'info_dict': {
'id': '28853',
'title': 'The Bone Zone',
'description': 'Podcast by The Bone Zone',
'description': r're:The official home of the Bone Zone podcast.+',
},
'playlist_count': 275,
'playlist_mincount': 275,
}, {
'url': 'https://www.podchaser.com/podcasts/sean-carrolls-mindscape-scienc-699349/episodes',
'info_dict': {
@@ -51,19 +54,33 @@ class PodchaserIE(InfoExtractor):
@staticmethod
def _parse_episode(episode, podcast):
return {
'id': str(episode.get('id')),
'title': episode.get('title'),
'description': episode.get('description'),
'url': episode.get('audio_url'),
'thumbnail': episode.get('image_url'),
'duration': str_to_int(episode.get('length')),
'timestamp': unified_timestamp(episode.get('air_date')),
'average_rating': float_or_none(episode.get('rating')),
'categories': list(set(traverse_obj(podcast, (('summary', None), 'categories', ..., 'text')))),
'tags': traverse_obj(podcast, ('tags', ..., 'text')),
'series': podcast.get('title'),
}
info = traverse_obj(episode, {
'id': ('id', {int}, {str_or_none}, {require('episode ID')}),
'title': ('title', {str}),
'description': ('description', {str}),
'url': ('audio_url', {url_or_none}),
'thumbnail': ('image_url', {url_or_none}),
'duration': ('length', {int_or_none}),
'timestamp': ('air_date', {unified_timestamp}),
'average_rating': ('rating', {float_or_none}),
})
info.update(traverse_obj(podcast, {
'series': ('title', {str}),
'series_id': ('id', {int}, {str_or_none}),
'categories': (('summary', None), 'categories', ..., 'text', {str}, filter, all, {orderedSet}),
'tags': ('tags', ..., 'text', {str}),
}))
info['vcodec'] = 'none'
if info.get('series_id'):
podcast_slug = traverse_obj(podcast, ('slug', {str})) or 'podcast'
episode_slug = traverse_obj(episode, ('slug', {str})) or 'episode'
info['webpage_url'] = '/'.join((
'https://www.podchaser.com/podcasts',
'-'.join((podcast_slug[:30].rstrip('-'), info['series_id'])),
'-'.join((episode_slug[:30].rstrip('-'), info['id']))))
return info
def _call_api(self, path, *args, **kwargs):
return self._download_json(f'https://api.podchaser.com/{path}', *args, **kwargs)
@@ -93,5 +110,5 @@ class PodchaserIE(InfoExtractor):
OnDemandPagedList(functools.partial(self._fetch_page, podcast_id, podcast), self._PAGE_SIZE),
str_or_none(podcast.get('id')), podcast.get('title'), podcast.get('description'))
episode = self._call_api(f'episodes/{episode_id}', episode_id)
episode = self._call_api(f'podcasts/{podcast_id}/episodes/{episode_id}/player_ids', episode_id)
return self._parse_episode(episode, podcast)

View File

@@ -0,0 +1,121 @@
import json
import urllib.parse
from .common import InfoExtractor
from ..utils import (
float_or_none,
int_or_none,
str_or_none,
try_call,
url_or_none,
)
from ..utils.traversal import find_element, traverse_obj
class ToutiaoIE(InfoExtractor):
IE_NAME = 'toutiao'
IE_DESC = '今日头条'
_VALID_URL = r'https?://www\.toutiao\.com/video/(?P<id>\d+)/?(?:[?#]|$)'
_TESTS = [{
'url': 'https://www.toutiao.com/video/7505382061495176511/',
'info_dict': {
'id': '7505382061495176511',
'ext': 'mp4',
'title': '新疆多地现不明飞行物,目击者称和月亮一样亮,几秒内突然加速消失,气象部门回应',
'comment_count': int,
'duration': 9.753,
'like_count': int,
'release_date': '20250517',
'release_timestamp': 1747483344,
'thumbnail': r're:https?://p\d+-sign\.toutiaoimg\.com/.+$',
'uploader': '极目新闻',
'uploader_id': 'MS4wLjABAAAAeateBb9Su8I3MJOZozmvyzWktmba5LMlliRDz1KffnM',
'view_count': int,
},
}, {
'url': 'https://www.toutiao.com/video/7479446610359878153/',
'info_dict': {
'id': '7479446610359878153',
'ext': 'mp4',
'title': '小伙竟然利用两块磁铁制作成磁力减震器,简直太有创意了!',
'comment_count': int,
'duration': 118.374,
'like_count': int,
'release_date': '20250308',
'release_timestamp': 1741444368,
'thumbnail': r're:https?://p\d+-sign\.toutiaoimg\.com/.+$',
'uploader': '小莉创意发明',
'uploader_id': 'MS4wLjABAAAA4f7d4mwtApALtHIiq-QM20dwXqe32NUz0DeWF7wbHKw',
'view_count': int,
},
}]
def _real_initialize(self):
if self._get_cookies('https://www.toutiao.com').get('ttwid'):
return
urlh = self._request_webpage(
'https://ttwid.bytedance.com/ttwid/union/register/', None,
'Fetching ttwid', 'Unable to fetch ttwid', headers={
'Content-Type': 'application/json',
}, data=json.dumps({
'aid': 24,
'needFid': False,
'region': 'cn',
'service': 'www.toutiao.com',
'union': True,
}).encode(),
)
if ttwid := try_call(lambda: self._get_cookies(urlh.url)['ttwid'].value):
self._set_cookie('.toutiao.com', 'ttwid', ttwid)
return
self.raise_login_required()
def _real_extract(self, url):
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id)
video_data = traverse_obj(webpage, (
{find_element(tag='script', id='RENDER_DATA')},
{urllib.parse.unquote}, {json.loads}, 'data', 'initialVideo',
))
formats = []
for video in traverse_obj(video_data, (
'videoPlayInfo', 'video_list', lambda _, v: v['main_url'],
)):
formats.append({
'url': video['main_url'],
**traverse_obj(video, ('video_meta', {
'acodec': ('audio_profile', {str}),
'asr': ('audio_sample_rate', {int_or_none}),
'audio_channels': ('audio_channels', {float_or_none}, {int_or_none}),
'ext': ('vtype', {str}),
'filesize': ('size', {int_or_none}),
'format_id': ('definition', {str}),
'fps': ('fps', {int_or_none}),
'height': ('vheight', {int_or_none}),
'tbr': ('real_bitrate', {float_or_none(scale=1000)}),
'vcodec': ('codec_type', {str}),
'width': ('vwidth', {int_or_none}),
})),
})
return {
'id': video_id,
'formats': formats,
**traverse_obj(video_data, {
'comment_count': ('commentCount', {int_or_none}),
'duration': ('videoPlayInfo', 'video_duration', {float_or_none}),
'like_count': ('repinCount', {int_or_none}),
'release_timestamp': ('publishTime', {int_or_none}),
'thumbnail': (('poster', 'coverUrl'), {url_or_none}, any),
'title': ('title', {str}),
'uploader': ('userInfo', 'name', {str}),
'uploader_id': ('userInfo', 'userId', {str_or_none}),
'view_count': ('playCount', {int_or_none}),
'webpage_url': ('detailUrl', {url_or_none}),
}),
}

View File

@@ -1,4 +1,5 @@
import base64
import hashlib
import itertools
import re
@@ -16,6 +17,7 @@ from ..utils import (
str_to_int,
try_get,
unified_timestamp,
update_url_query,
url_or_none,
urlencode_postdata,
urljoin,
@@ -171,6 +173,10 @@ class TwitCastingIE(InfoExtractor):
'player': 'pc_web',
})
password_params = {
'word': hashlib.md5(video_password.encode()).hexdigest(),
} if video_password else None
formats = []
# low: 640x360, medium: 1280x720, high: 1920x1080
qq = qualities(['low', 'medium', 'high'])
@@ -178,7 +184,7 @@ class TwitCastingIE(InfoExtractor):
'tc-hls', 'streams', {dict.items}, lambda _, v: url_or_none(v[1]),
)):
formats.append({
'url': m3u8_url,
'url': update_url_query(m3u8_url, password_params),
'format_id': f'hls-{quality}',
'ext': 'mp4',
'quality': qq(quality),
@@ -192,7 +198,7 @@ class TwitCastingIE(InfoExtractor):
'llfmp4', 'streams', {dict.items}, lambda _, v: url_or_none(v[1]),
)):
formats.append({
'url': ws_url,
'url': update_url_query(ws_url, password_params),
'format_id': f'ws-{mode}',
'ext': 'mp4',
'quality': qq(mode),

View File

@@ -3950,19 +3950,23 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
subtitles = {}
skipped_subs_clients = set()
prs = traverse_obj(player_responses, (
# Filter out initial_pr which does not have streamingData (smuggled client context)
lambda _, v: v['streamingData'] and v['captions']['playerCaptionsTracklistRenderer']))
pctrs = traverse_obj(prs, (..., 'captions', 'playerCaptionsTracklistRenderer', {dict}))
# Only web/mweb clients provide translationLanguages, so include initial_pr in the traversal
translation_languages = {
lang.get('languageCode'): self._get_text(lang.get('languageName'), max_runs=1)
for lang in traverse_obj(pctrs, (..., 'translationLanguages', ..., {dict}))}
lang['languageCode']: self._get_text(lang['languageName'], max_runs=1)
for lang in traverse_obj(player_responses, (
..., 'captions', 'playerCaptionsTracklistRenderer', 'translationLanguages',
lambda _, v: v['languageCode'] and v['languageName']))
}
# NB: Constructing the full subtitle dictionary is slow
get_translated_subs = 'translated_subs' not in self._configuration_arg('skip') and (
self.get_param('writeautomaticsub', False) or self.get_param('listsubtitles'))
all_captions = traverse_obj(pctrs, (..., 'captionTracks', ..., {dict}))
# Filter out initial_pr which does not have streamingData (smuggled client context)
prs = traverse_obj(player_responses, (
lambda _, v: v['streamingData'] and v['captions']['playerCaptionsTracklistRenderer']))
all_captions = traverse_obj(prs, (
..., 'captions', 'playerCaptionsTracklistRenderer', 'captionTracks', ..., {dict}))
need_subs_langs = {get_lang_code(sub) for sub in all_captions if sub.get('kind') != 'asr'}
need_caps_langs = {
remove_start(get_lang_code(sub), 'a-')