diff --git a/.github/update.log b/.github/update.log index 1d1cdfa66e..beb1afbcc0 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1103,3 +1103,4 @@ Update On Sun Aug 24 20:36:09 CEST 2025 Update On Mon Aug 25 20:39:10 CEST 2025 Update On Tue Aug 26 20:39:16 CEST 2025 Update On Wed Aug 27 20:38:26 CEST 2025 +Update On Thu Aug 28 20:40:03 CEST 2025 diff --git a/clash-meta/go.mod b/clash-meta/go.mod index f7a63fa75a..494a9a4255 100644 --- a/clash-meta/go.mod +++ b/clash-meta/go.mod @@ -31,7 +31,7 @@ require ( github.com/metacubex/sing-shadowsocks2 v0.2.6 github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 github.com/metacubex/sing-tun v0.4.8-0.20250827085914-fc5681b9fc9f - github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 + github.com/metacubex/sing-vmess v0.2.4-0.20250828081059-57e77685eef9 github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f github.com/metacubex/smux v0.0.0-20250503055512-501391591dee github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617 diff --git a/clash-meta/go.sum b/clash-meta/go.sum index 9b62944f4c..3d3d82b1a2 100644 --- a/clash-meta/go.sum +++ b/clash-meta/go.sum @@ -131,8 +131,8 @@ github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MY github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= github.com/metacubex/sing-tun v0.4.8-0.20250827085914-fc5681b9fc9f h1:1MV/pFn2vjnyvH/0u6sJST0kmaoZXgbUytCCfuelhl8= github.com/metacubex/sing-tun v0.4.8-0.20250827085914-fc5681b9fc9f/go.mod h1:FQ9zXA+kVhdzqgFqeJdi/AUhJgUgw+SUXqrR++GvbnM= -github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 h1:WZepq4TOZa6WewB8tGAZrrL+bL2R2ivoBzuEgAeolWc= -github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= +github.com/metacubex/sing-vmess v0.2.4-0.20250828081059-57e77685eef9 h1:VP7rBmRJUqBpP8uJQpzEqCgnbAYWbz2QtWqoBdNrmwU= +github.com/metacubex/sing-vmess v0.2.4-0.20250828081059-57e77685eef9/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo= diff --git a/clash-meta/transport/vless/encryption/client.go b/clash-meta/transport/vless/encryption/client.go index 465f16260f..8debb06f64 100644 --- a/clash-meta/transport/vless/encryption/client.go +++ b/clash-meta/transport/vless/encryption/client.go @@ -65,7 +65,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { if i.NfsPKeys == nil { return nil, errors.New("uninitialized") } - c := &CommonConn{Conn: conn} + c := NewCommonConn(conn) ivAndRealysLength := 16 + i.RelaysLength pfsKeyExchangeLength := 18 + 1184 + 32 + 16 diff --git a/clash-meta/transport/vless/encryption/common.go b/clash-meta/transport/vless/encryption/common.go index 1b2aa18201..0275a21fa3 100644 --- a/clash-meta/transport/vless/encryption/common.go +++ b/clash-meta/transport/vless/encryption/common.go @@ -4,16 +4,16 @@ import ( "bytes" "crypto/aes" "crypto/cipher" - "crypto/rand" "errors" "fmt" "io" - "math/big" "net" - "strings" "time" + "github.com/metacubex/mihomo/common/pool" + "github.com/metacubex/blake3" + "github.com/metacubex/randv2" ) type CommonConn struct { @@ -23,36 +23,44 @@ type CommonConn struct { PreWrite []byte GCM *GCM PeerPadding []byte + rawInput bytes.Buffer // PeerInBytes PeerGCM *GCM input bytes.Reader // PeerCache } +func NewCommonConn(conn net.Conn) *CommonConn { + return &CommonConn{ + Conn: conn, + } +} + func (c *CommonConn) Write(b []byte) (int, error) { if len(b) == 0 { return 0, nil } - var data []byte + outBytes := pool.Get(5 + 8192 + 16) + defer pool.Put(outBytes) for n := 0; n < len(b); { b := b[n:] if len(b) > 8192 { b = b[:8192] // for avoiding another copy() in peer's Read() } n += len(b) - data = make([]byte, 5+len(b)+16) - EncodeHeader(data, len(b)+16) + headerAndData := outBytes[:5+len(b)+16] + EncodeHeader(headerAndData, len(b)+16) max := false if bytes.Equal(c.GCM.Nonce[:], MaxNonce) { max = true } - c.GCM.Seal(data[:5], nil, b, data[:5]) + c.GCM.Seal(headerAndData[:5], nil, b, headerAndData[:5]) if max { - c.GCM = NewGCM(data[5:], c.UnitedKey) + c.GCM = NewGCM(headerAndData, c.UnitedKey) } if c.PreWrite != nil { - data = append(c.PreWrite, data...) + headerAndData = append(c.PreWrite, headerAndData...) c.PreWrite = nil } - if _, err := c.Conn.Write(data); err != nil { + if _, err := c.Conn.Write(headerAndData); err != nil { return 0, err } } @@ -85,9 +93,13 @@ func (c *CommonConn) Read(b []byte) (int, error) { if c.input.Len() > 0 { return c.input.Read(b) } - h, l, err := ReadAndDecodeHeader(c.Conn) // l: 17~17000 + peerHeader := make([]byte, 5) + if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { + return 0, err + } + l, err := DecodeHeader(peerHeader) // l: 17~17000 if err != nil { - if c.Client != nil && strings.HasPrefix(err.Error(), "invalid header: ") { // client's 0-RTT + if c.Client != nil && errors.Is(err, ErrInvalidHeader) { // client's 0-RTT c.Client.RWLock.Lock() if bytes.HasPrefix(c.UnitedKey, c.Client.PfsKey) { c.Client.Expire = time.Now() // expired @@ -98,7 +110,8 @@ func (c *CommonConn) Read(b []byte) (int, error) { return 0, err } c.Client = nil - peerData := make([]byte, l) + c.rawInput.Grow(l) + peerData := c.rawInput.Bytes()[:l] if _, err := io.ReadFull(c.Conn, peerData); err != nil { return 0, err } @@ -108,9 +121,9 @@ func (c *CommonConn) Read(b []byte) (int, error) { } var newGCM *GCM if bytes.Equal(c.PeerGCM.Nonce[:], MaxNonce) { - newGCM = NewGCM(peerData, c.UnitedKey) + newGCM = NewGCM(append(peerHeader, peerData...), c.UnitedKey) } - _, err = c.PeerGCM.Open(dst[:0], nil, peerData, h) + _, err = c.PeerGCM.Open(dst[:0], nil, peerData, peerHeader) if newGCM != nil { c.PeerGCM = newGCM } @@ -180,41 +193,22 @@ func EncodeHeader(h []byte, l int) { h[4] = byte(l) } +var ErrInvalidHeader = errors.New("invalid header") + func DecodeHeader(h []byte) (l int, err error) { l = int(h[3])<<8 | int(h[4]) if h[0] != 23 || h[1] != 3 || h[2] != 3 { l = 0 } if l < 17 || l > 17000 { // TODO: TLSv1.3 max length - err = fmt.Errorf("invalid header: %v", h[:5]) // DO NOT CHANGE: relied by client's Read() + err = fmt.Errorf("%w: %v", ErrInvalidHeader, h[:5]) // DO NOT CHANGE: relied by client's Read() } return } -func ReadAndDecodeHeader(conn net.Conn) (h []byte, l int, err error) { - h = make([]byte, 5) - if _, err = io.ReadFull(conn, h); err != nil { - return - } - l, err = DecodeHeader(h) - return -} - -func ReadAndDiscardPaddings(conn net.Conn) (h []byte, l int, err error) { - for { - if h, l, err = ReadAndDecodeHeader(conn); err != nil { - return - } - if _, err = io.ReadFull(conn, make([]byte, l)); err != nil { - return - } - } -} - func randBetween(from int64, to int64) int64 { if from == to { return from } - bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from)) - return from + bigInt.Int64() + return from + randv2.Int64N(to-from) } diff --git a/clash-meta/transport/vless/encryption/doc.go b/clash-meta/transport/vless/encryption/doc.go index 177839c64a..d3d72e5ad7 100644 --- a/clash-meta/transport/vless/encryption/doc.go +++ b/clash-meta/transport/vless/encryption/doc.go @@ -20,4 +20,5 @@ // https://github.com/XTLS/Xray-core/commit/ad7140641c44239c9dcdc3d7215ea639b1f0841c // https://github.com/XTLS/Xray-core/commit/0199dea39988a1a1b846d0bf8598631bade40902 // https://github.com/XTLS/Xray-core/commit/fce1195b60f48ca18a953dbd5c7d991869de9a5e +// https://github.com/XTLS/Xray-core/commit/b0b220985c9c1bc832665458d5fd6e0c287b67ae package encryption diff --git a/clash-meta/transport/vless/encryption/server.go b/clash-meta/transport/vless/encryption/server.go index 36ce399ab5..14ffd1e503 100644 --- a/clash-meta/transport/vless/encryption/server.go +++ b/clash-meta/transport/vless/encryption/server.go @@ -101,7 +101,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { if i.NfsSKeys == nil { return nil, errors.New("uninitialized") } - c := &CommonConn{Conn: conn} + c := NewCommonConn(conn) ivAndRelays := make([]byte, 16+i.RelaysLength) if _, err := io.ReadFull(conn, ivAndRelays); err != nil { diff --git a/clash-meta/transport/vless/vision/conn.go b/clash-meta/transport/vless/vision/conn.go index 0c300e05d1..7e778cf84c 100644 --- a/clash-meta/transport/vless/vision/conn.go +++ b/clash-meta/transport/vless/vision/conn.go @@ -125,6 +125,7 @@ func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { } if vc.input.Len() == 0 { needReturn = true + *vc.input = bytes.Reader{} // full reset vc.input = nil } else { // buffer is full return nil @@ -139,6 +140,7 @@ func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { } needReturn = true if vc.rawInput.Len() == 0 { + *vc.rawInput = bytes.Buffer{} // full reset vc.rawInput = nil } } diff --git a/clash-nyanpasu/backend/Cargo.lock b/clash-nyanpasu/backend/Cargo.lock index b05a715b3c..d6f3a33f08 100644 --- a/clash-nyanpasu/backend/Cargo.lock +++ b/clash-nyanpasu/backend/Cargo.lock @@ -1466,9 +1466,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.45" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" +checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" dependencies = [ "clap_builder", "clap_derive", @@ -1476,9 +1476,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.44" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" dependencies = [ "anstream", "anstyle", @@ -2329,7 +2329,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] diff --git a/clash-nyanpasu/frontend/interface/package.json b/clash-nyanpasu/frontend/interface/package.json index 7900e27a2e..bf5ba01c28 100644 --- a/clash-nyanpasu/frontend/interface/package.json +++ b/clash-nyanpasu/frontend/interface/package.json @@ -22,6 +22,6 @@ }, "devDependencies": { "@types/lodash-es": "4.17.12", - "@types/react": "19.1.11" + "@types/react": "19.1.12" } } diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index a600c33490..5b368c551f 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -56,7 +56,7 @@ "@csstools/normalize.css": "12.1.1", "@emotion/babel-plugin": "11.13.5", "@emotion/react": "11.14.0", - "@iconify/json": "2.2.378", + "@iconify/json": "2.2.379", "@monaco-editor/react": "4.7.0", "@tanstack/react-query": "5.85.5", "@tanstack/react-router": "1.131.28", @@ -70,7 +70,7 @@ "@tauri-apps/plugin-process": "2.3.0", "@tauri-apps/plugin-shell": "2.3.0", "@tauri-apps/plugin-updater": "2.9.0", - "@types/react": "19.1.11", + "@types/react": "19.1.12", "@types/react-dom": "19.1.8", "@types/validator": "13.15.2", "@vitejs/plugin-legacy": "7.2.1", diff --git a/clash-nyanpasu/frontend/ui/package.json b/clash-nyanpasu/frontend/ui/package.json index b0df97f8d9..dc5e888bef 100644 --- a/clash-nyanpasu/frontend/ui/package.json +++ b/clash-nyanpasu/frontend/ui/package.json @@ -19,7 +19,7 @@ "@radix-ui/react-scroll-area": "1.2.10", "@tauri-apps/api": "2.6.0", "@types/d3": "7.4.3", - "@types/react": "19.1.11", + "@types/react": "19.1.12", "@vitejs/plugin-react": "5.0.1", "ahooks": "3.9.4", "d3": "7.9.0", diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index 7819522200..5e376bc6fd 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -1,8 +1,8 @@ { "manifest_version": 1, "latest": { - "mihomo": "v1.19.12", - "mihomo_alpha": "alpha-bf8d1e1", + "mihomo": "v1.19.13", + "mihomo_alpha": "alpha-0ced98d", "clash_rs": "v0.9.0", "clash_premium": "2023-09-05-gdcc8d87", "clash_rs_alpha": "0.9.0-alpha+sha.8791f7f" @@ -69,5 +69,5 @@ "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" } }, - "updated_at": "2025-08-26T22:20:55.772Z" + "updated_at": "2025-08-27T22:20:55.567Z" } diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index 1fb0c30c25..7df3093d9d 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -204,8 +204,8 @@ importers: specifier: 4.17.12 version: 4.17.12 '@types/react': - specifier: 19.1.11 - version: 19.1.11 + specifier: 19.1.12 + version: 19.1.12 frontend/nyanpasu: dependencies: @@ -220,7 +220,7 @@ importers: version: 3.2.2(react@19.1.1) '@emotion/styled': specifier: 11.14.1 - version: 11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) + version: 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) '@juggle/resize-observer': specifier: 3.4.0 version: 3.4.0 @@ -229,16 +229,16 @@ importers: version: 0.3.0 '@mui/icons-material': specifier: 7.3.1 - version: 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) + version: 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) '@mui/lab': specifier: 7.0.0-beta.16 - version: 7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@mui/material': specifier: 7.3.1 - version: 7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@mui/x-date-pickers': specifier: 8.10.2 - version: 8.10.2(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 8.10.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@nyanpasu/interface': specifier: workspace:^ version: link:../interface @@ -280,19 +280,19 @@ importers: version: 25.4.2(typescript@5.9.2) jotai: specifier: 2.13.1 - version: 2.13.1(@babel/core@7.26.10)(@babel/template@7.27.2)(@types/react@19.1.11)(react@19.1.1) + version: 2.13.1(@babel/core@7.26.10)(@babel/template@7.27.2)(@types/react@19.1.12)(react@19.1.1) json-schema: specifier: 0.4.0 version: 0.4.0 material-react-table: specifier: npm:@greenhat616/material-react-table@4.0.0 - version: '@greenhat616/material-react-table@4.0.0(36e8a9be47c46ccf4979f4dd5dc24e25)' + version: '@greenhat616/material-react-table@4.0.0(8c5184cacde5eaa7b33c3b8210f40105)' monaco-editor: specifier: 0.52.2 version: 0.52.2 mui-color-input: specifier: 7.0.0 - version: 7.0.0(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 7.0.0(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: specifier: 19.1.1 version: 19.1.1 @@ -307,13 +307,13 @@ importers: version: 1.6.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-hook-form-mui: specifier: 7.6.2 - version: 7.6.2(9e88d804d29cfb33862d8a7cbbc03161) + version: 7.6.2(58abb2a4187b63d3bc2ea3bcc4bbaf7a) react-i18next: specifier: 15.7.2 version: 15.7.2(i18next@25.4.2(typescript@5.9.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.9.2) react-markdown: specifier: 10.1.0 - version: 10.1.0(@types/react@19.1.11)(react@19.1.1) + version: 10.1.0(@types/react@19.1.12)(react@19.1.1) react-split-grid: specifier: 1.0.4 version: 1.0.4(react@19.1.1) @@ -341,10 +341,10 @@ importers: version: 11.13.5 '@emotion/react': specifier: 11.14.0 - version: 11.14.0(@types/react@19.1.11)(react@19.1.1) + version: 11.14.0(@types/react@19.1.12)(react@19.1.1) '@iconify/json': - specifier: 2.2.378 - version: 2.2.378 + specifier: 2.2.379 + version: 2.2.379 '@monaco-editor/react': specifier: 4.7.0 version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -385,11 +385,11 @@ importers: specifier: 2.9.0 version: 2.9.0 '@types/react': - specifier: 19.1.11 - version: 19.1.11 + specifier: 19.1.12 + version: 19.1.12 '@types/react-dom': specifier: 19.1.8 - version: 19.1.8(@types/react@19.1.11) + version: 19.1.8(@types/react@19.1.12) '@types/validator': specifier: 13.15.2 version: 13.15.2 @@ -464,19 +464,19 @@ importers: version: 0.3.0 '@mui/icons-material': specifier: 7.3.1 - version: 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) + version: 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) '@mui/lab': specifier: 7.0.0-beta.16 - version: 7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@mui/material': specifier: 7.3.1 - version: 7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-portal': specifier: 1.1.9 - version: 1.1.9(@types/react-dom@19.1.8(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.1.9(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-scroll-area': specifier: 1.2.10 - version: 1.2.10(@types/react-dom@19.1.8(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.2.10(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tauri-apps/api': specifier: 2.6.0 version: 2.6.0 @@ -484,8 +484,8 @@ importers: specifier: 7.4.3 version: 7.4.3 '@types/react': - specifier: 19.1.11 - version: 19.1.11 + specifier: 19.1.12 + version: 19.1.12 '@vitejs/plugin-react': specifier: 5.0.1 version: 5.0.1(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(less@4.2.0)(lightningcss@1.30.1)(sass-embedded@1.90.0)(sass@1.90.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)) @@ -525,7 +525,7 @@ importers: devDependencies: '@emotion/react': specifier: 11.14.0 - version: 11.14.0(@types/react@19.1.11)(react@19.1.1) + version: 11.14.0(@types/react@19.1.12)(react@19.1.1) '@types/d3-interpolate-path': specifier: 2.0.3 version: 2.0.3 @@ -1799,8 +1799,8 @@ packages: prettier-plugin-ember-template-tag: optional: true - '@iconify/json@2.2.378': - resolution: {integrity: sha512-mgz/rZVUIwq3btynvlHdUwrZbcb3KUSH97ehpOOq+t8hxTBsIwkkZf1ZgLaseNCSz1OrA+pcULsPDVl3h2odQg==} + '@iconify/json@2.2.379': + resolution: {integrity: sha512-PInpWLQi2C+fDIbBdVNcKOj9QKl7TT6sXqFqYMa4e34sMx206PiUlg0puWgo1Q1C/TDNQiy/raGWUbssOb1eyg==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -3442,8 +3442,8 @@ packages: peerDependencies: '@types/react': '*' - '@types/react@19.1.11': - resolution: {integrity: sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==} + '@types/react@19.1.12': + resolution: {integrity: sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==} '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} @@ -9938,7 +9938,7 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1)': + '@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1)': dependencies: '@babel/runtime': 7.26.0 '@emotion/babel-plugin': 11.13.5 @@ -9950,7 +9950,7 @@ snapshots: hoist-non-react-statics: 3.3.2 react: 19.1.1 optionalDependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 transitivePeerDependencies: - supports-color @@ -9964,18 +9964,18 @@ snapshots: '@emotion/sheet@1.4.0': {} - '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1)': + '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)': dependencies: '@babel/runtime': 7.27.6 '@emotion/babel-plugin': 11.13.5 '@emotion/is-prop-valid': 1.3.0 - '@emotion/react': 11.14.0(@types/react@19.1.11)(react@19.1.1) + '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1) '@emotion/serialize': 1.3.3 '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.1) '@emotion/utils': 1.4.2 react: 19.1.1 optionalDependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 transitivePeerDependencies: - supports-color @@ -10121,13 +10121,13 @@ snapshots: '@fastify/busboy@2.1.1': {} - '@greenhat616/material-react-table@4.0.0(36e8a9be47c46ccf4979f4dd5dc24e25)': + '@greenhat616/material-react-table@4.0.0(8c5184cacde5eaa7b33c3b8210f40105)': dependencies: - '@emotion/react': 11.14.0(@types/react@19.1.11)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) - '@mui/icons-material': 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) - '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@mui/x-date-pickers': 8.10.2(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) + '@mui/icons-material': 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) + '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@mui/x-date-pickers': 8.10.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tanstack/match-sorter-utils': 8.19.4 '@tanstack/react-table': 8.21.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tanstack/react-virtual': 3.13.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -10163,7 +10163,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@iconify/json@2.2.378': + '@iconify/json@2.2.379': dependencies: '@iconify/types': 2.0.0 pathe: 1.1.2 @@ -10274,39 +10274,39 @@ snapshots: '@mui/core-downloads-tracker@7.3.1': {} - '@mui/icons-material@7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.11)(react@19.1.1)': + '@mui/icons-material@7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 - '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 optionalDependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 - '@mui/lab@7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@mui/lab@7.0.0-beta.16(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 - '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) - '@mui/types': 7.4.5(@types/react@19.1.11) - '@mui/utils': 7.3.1(@types/react@19.1.11)(react@19.1.1) + '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) + '@mui/types': 7.4.5(@types/react@19.1.12) + '@mui/utils': 7.3.1(@types/react@19.1.12)(react@19.1.1) clsx: 2.1.1 prop-types: 15.8.1 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.11)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) - '@types/react': 19.1.11 + '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) + '@types/react': 19.1.12 - '@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 '@mui/core-downloads-tracker': 7.3.1 - '@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) - '@mui/types': 7.4.5(@types/react@19.1.11) - '@mui/utils': 7.3.1(@types/react@19.1.11)(react@19.1.1) + '@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) + '@mui/types': 7.4.5(@types/react@19.1.12) + '@mui/utils': 7.3.1(@types/react@19.1.12)(react@19.1.1) '@popperjs/core': 2.11.8 - '@types/react-transition-group': 4.4.12(@types/react@19.1.11) + '@types/react-transition-group': 4.4.12(@types/react@19.1.12) clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 @@ -10315,20 +10315,20 @@ snapshots: react-is: 19.1.1 react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.11)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) - '@types/react': 19.1.11 + '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) + '@types/react': 19.1.12 - '@mui/private-theming@7.3.1(@types/react@19.1.11)(react@19.1.1)': + '@mui/private-theming@7.3.1(@types/react@19.1.12)(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 - '@mui/utils': 7.3.1(@types/react@19.1.11)(react@19.1.1) + '@mui/utils': 7.3.1(@types/react@19.1.12)(react@19.1.1) prop-types: 15.8.1 react: 19.1.1 optionalDependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 - '@mui/styled-engine@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(react@19.1.1)': + '@mui/styled-engine@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 '@emotion/cache': 11.14.0 @@ -10338,67 +10338,67 @@ snapshots: prop-types: 15.8.1 react: 19.1.1 optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.11)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) + '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) - '@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1)': + '@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 - '@mui/private-theming': 7.3.1(@types/react@19.1.11)(react@19.1.1) - '@mui/styled-engine': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(react@19.1.1) - '@mui/types': 7.4.5(@types/react@19.1.11) - '@mui/utils': 7.3.1(@types/react@19.1.11)(react@19.1.1) + '@mui/private-theming': 7.3.1(@types/react@19.1.12)(react@19.1.1) + '@mui/styled-engine': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(react@19.1.1) + '@mui/types': 7.4.5(@types/react@19.1.12) + '@mui/utils': 7.3.1(@types/react@19.1.12)(react@19.1.1) clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 react: 19.1.1 optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.11)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) - '@types/react': 19.1.11 + '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) + '@types/react': 19.1.12 - '@mui/types@7.4.5(@types/react@19.1.11)': + '@mui/types@7.4.5(@types/react@19.1.12)': dependencies: '@babel/runtime': 7.28.2 optionalDependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 - '@mui/utils@7.3.1(@types/react@19.1.11)(react@19.1.1)': + '@mui/utils@7.3.1(@types/react@19.1.12)(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 - '@mui/types': 7.4.5(@types/react@19.1.11) + '@mui/types': 7.4.5(@types/react@19.1.12) '@types/prop-types': 15.7.15 clsx: 2.1.1 prop-types: 15.8.1 react: 19.1.1 react-is: 19.1.1 optionalDependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 - '@mui/x-date-pickers@8.10.2(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@mui/x-date-pickers@8.10.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 - '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) - '@mui/utils': 7.3.1(@types/react@19.1.11)(react@19.1.1) - '@mui/x-internals': 8.10.2(@types/react@19.1.11)(react@19.1.1) - '@types/react-transition-group': 4.4.12(@types/react@19.1.11) + '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@mui/system': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) + '@mui/utils': 7.3.1(@types/react@19.1.12)(react@19.1.1) + '@mui/x-internals': 8.10.2(@types/react@19.1.12)(react@19.1.1) + '@types/react-transition-group': 4.4.12(@types/react@19.1.12) clsx: 2.1.1 prop-types: 15.8.1 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.11)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) + '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) dayjs: 1.11.13 transitivePeerDependencies: - '@types/react' - '@mui/x-internals@8.10.2(@types/react@19.1.11)(react@19.1.1)': + '@mui/x-internals@8.10.2(@types/react@19.1.12)(react@19.1.1)': dependencies: '@babel/runtime': 7.28.2 - '@mui/utils': 7.3.1(@types/react@19.1.11)(react@19.1.1) + '@mui/utils': 7.3.1(@types/react@19.1.12)(react@19.1.1) react: 19.1.1 reselect: 5.1.1 use-sync-external-store: 1.5.0(react@19.1.1) @@ -10818,88 +10818,88 @@ snapshots: '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.11)(react@19.1.1)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.12)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 - '@radix-ui/react-context@1.1.2(@types/react@19.1.11)(react@19.1.1)': + '@radix-ui/react-context@1.1.2(@types/react@19.1.12)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 - '@radix-ui/react-direction@1.1.1(@types/react@19.1.11)(react@19.1.1)': + '@radix-ui/react-direction@1.1.1(@types/react@19.1.12)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.8(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.8(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.11)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: - '@types/react': 19.1.11 - '@types/react-dom': 19.1.8(@types/react@19.1.11) + '@types/react': 19.1.12 + '@types/react-dom': 19.1.8(@types/react@19.1.12) - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.8(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.11)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.11)(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: - '@types/react': 19.1.11 - '@types/react-dom': 19.1.8(@types/react@19.1.11) + '@types/react': 19.1.12 + '@types/react-dom': 19.1.8(@types/react@19.1.12) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.8(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.11)(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: - '@types/react': 19.1.11 - '@types/react-dom': 19.1.8(@types/react@19.1.11) + '@types/react': 19.1.12 + '@types/react-dom': 19.1.8(@types/react@19.1.12) - '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.1.8(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.11)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.11)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.11)(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.8(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.8(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.11)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.11)(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.8(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: - '@types/react': 19.1.11 - '@types/react-dom': 19.1.8(@types/react@19.1.11) + '@types/react': 19.1.12 + '@types/react-dom': 19.1.8(@types/react@19.1.12) - '@radix-ui/react-slot@1.2.3(@types/react@19.1.11)(react@19.1.1)': + '@radix-ui/react-slot@1.2.3(@types/react@19.1.12)(react@19.1.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.11)(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) react: 19.1.1 optionalDependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.11)(react@19.1.1)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.12)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.11)(react@19.1.1)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.12)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 '@rolldown/pluginutils@1.0.0-beta.32': {} @@ -11739,15 +11739,15 @@ snapshots: '@types/prop-types@15.7.15': {} - '@types/react-dom@19.1.8(@types/react@19.1.11)': + '@types/react-dom@19.1.8(@types/react@19.1.12)': dependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 - '@types/react-transition-group@4.4.12(@types/react@19.1.11)': + '@types/react-transition-group@4.4.12(@types/react@19.1.12)': dependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 - '@types/react@19.1.11': + '@types/react@19.1.12': dependencies: csstype: 3.1.3 @@ -14716,11 +14716,11 @@ snapshots: jju@1.4.0: {} - jotai@2.13.1(@babel/core@7.26.10)(@babel/template@7.27.2)(@types/react@19.1.11)(react@19.1.1): + jotai@2.13.1(@babel/core@7.26.10)(@babel/template@7.27.2)(@types/react@19.1.12)(react@19.1.1): optionalDependencies: '@babel/core': 7.26.10 '@babel/template': 7.27.2 - '@types/react': 19.1.11 + '@types/react': 19.1.12 react: 19.1.1 js-cookie@2.2.1: {} @@ -15361,16 +15361,16 @@ snapshots: muggle-string@0.4.1: {} - mui-color-input@7.0.0(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + mui-color-input@7.0.0(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: '@ctrl/tinycolor': 4.1.0 - '@emotion/react': 11.14.0(@types/react@19.1.11)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) - '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@emotion/react': 11.14.0(@types/react@19.1.12)(react@19.1.1) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) + '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 nano-css@5.6.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: @@ -15968,14 +15968,14 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - react-hook-form-mui@7.6.2(9e88d804d29cfb33862d8a7cbbc03161): + react-hook-form-mui@7.6.2(58abb2a4187b63d3bc2ea3bcc4bbaf7a): dependencies: - '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@mui/material': 7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 react-hook-form: 7.52.1(react@19.1.1) optionalDependencies: - '@mui/icons-material': 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.11)(react@19.1.1) - '@mui/x-date-pickers': 8.10.2(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(react@19.1.1))(@types/react@19.1.11)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@mui/icons-material': 7.3.1(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@19.1.12)(react@19.1.1) + '@mui/x-date-pickers': 8.10.2(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@mui/material@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@7.3.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(react@19.1.1))(@types/react@19.1.12)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-hook-form@7.52.1(react@19.1.1): dependencies: @@ -15995,11 +15995,11 @@ snapshots: react-is@19.1.1: {} - react-markdown@10.1.0(@types/react@19.1.11)(react@19.1.1): + react-markdown@10.1.0(@types/react@19.1.12)(react@19.1.1): dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.3 - '@types/react': 19.1.11 + '@types/react': 19.1.12 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.0 html-url-attributes: 3.0.0 diff --git a/lede/feeds.conf.default b/lede/feeds.conf.default index 933cb54fa8..90755bdc19 100644 --- a/lede/feeds.conf.default +++ b/lede/feeds.conf.default @@ -1,13 +1,12 @@ src-git packages https://github.com/coolsnowwolf/packages -#src-git luci https://github.com/coolsnowwolf/luci +#src-git luci https://github.com/coolsnowwolf/luci.git src-git luci https://github.com/coolsnowwolf/luci.git;openwrt-23.05 #src-git luci https://github.com/coolsnowwolf/luci.git;openwrt-24.10 src-git routing https://github.com/coolsnowwolf/routing src-git telephony https://github.com/coolsnowwolf/telephony.git -src-git helloworld https://github.com/fw876/helloworld.git -#src-git oui https://github.com/zhaojh329/oui.git +#src-git helloworld https://github.com/fw876/helloworld.git +#src-git qmodem https://github.com/FUjr/modem_feeds.git #src-git video https://github.com/openwrt/video.git #src-git targets https://github.com/openwrt/targets.git #src-git oldpackages http://git.openwrt.org/packages.git -#src-git qmodem https://github.com/FUjr/modem_feeds.git #src-link custom /usr/src/openwrt/custom-feed diff --git a/lede/target/linux/mediatek/dts/mt7981b-cmcc-rax3000m-emmc.dts b/lede/target/linux/mediatek/dts/mt7981b-cmcc-rax3000m-emmc.dts index dfe5ee46a1..9cb1a9e383 100644 --- a/lede/target/linux/mediatek/dts/mt7981b-cmcc-rax3000m-emmc.dts +++ b/lede/target/linux/mediatek/dts/mt7981b-cmcc-rax3000m-emmc.dts @@ -24,7 +24,6 @@ &mmc0 { bus-width = <8>; - cap-mmc-highspeed; max-frequency = <26000000>; non-removable; pinctrl-names = "default", "state_uhs"; @@ -56,15 +55,11 @@ }; macaddr_factory_24: macaddr@24 { - compatible = "mac-base"; reg = <0x24 0x6>; - #nvmem-cell-cells = <1>; }; macaddr_factory_2a: macaddr@2a { - compatible = "mac-base"; reg = <0x2a 0x6>; - #nvmem-cell-cells = <1>; }; }; }; @@ -88,8 +83,3 @@ }; }; }; - -&wifi { - nvmem-cells = <&eeprom_factory_0>; - nvmem-cell-names = "eeprom"; -}; diff --git a/lede/target/linux/mediatek/dts/mt7981b-cmcc-rax3000m-nand.dts b/lede/target/linux/mediatek/dts/mt7981b-cmcc-rax3000m-nand.dts index 0024bf939c..5cc96592b3 100644 --- a/lede/target/linux/mediatek/dts/mt7981b-cmcc-rax3000m-nand.dts +++ b/lede/target/linux/mediatek/dts/mt7981b-cmcc-rax3000m-nand.dts @@ -67,15 +67,11 @@ #size-cells = <1>; macaddr_factory_24: macaddr@24 { - compatible = "mac-base"; reg = <0x24 0x6>; - #nvmem-cell-cells = <1>; }; macaddr_factory_2a: macaddr@2a { - compatible = "mac-base"; reg = <0x2a 0x6>; - #nvmem-cell-cells = <1>; }; }; }; diff --git a/lede/target/linux/mediatek/dts/mt7981b-cmcc-xr30-emmc.dts b/lede/target/linux/mediatek/dts/mt7981b-cmcc-xr30-emmc.dts index a5ab7607a2..e5e5d0438a 100644 --- a/lede/target/linux/mediatek/dts/mt7981b-cmcc-xr30-emmc.dts +++ b/lede/target/linux/mediatek/dts/mt7981b-cmcc-xr30-emmc.dts @@ -56,15 +56,11 @@ }; macaddr_factory_24: macaddr@24 { - compatible = "mac-base"; reg = <0x24 0x6>; - #nvmem-cell-cells = <1>; }; macaddr_factory_2a: macaddr@2a { - compatible = "mac-base"; reg = <0x2a 0x6>; - #nvmem-cell-cells = <1>; }; }; }; @@ -88,8 +84,3 @@ }; }; }; - -&wifi { - nvmem-cells = <&eeprom_factory_0>; - nvmem-cell-names = "eeprom"; -}; diff --git a/lede/target/linux/mediatek/filogic/base-files/etc/hotplug.d/firmware/11-mt76-caldata b/lede/target/linux/mediatek/filogic/base-files/etc/hotplug.d/firmware/11-mt76-caldata index 4e7597eec9..1c7b947738 100644 --- a/lede/target/linux/mediatek/filogic/base-files/etc/hotplug.d/firmware/11-mt76-caldata +++ b/lede/target/linux/mediatek/filogic/base-files/etc/hotplug.d/firmware/11-mt76-caldata @@ -7,6 +7,14 @@ board=$(board_name) case "$FIRMWARE" in +"mediatek/mt7981_eeprom_mt7976_dbdc.bin") + case "$board" in + cmcc,rax3000m-emmc|\ + cmcc,xr30-emmc) + caldata_extract_mmc "factory" 0x0 0x1000 + ;; + esac + ;; "mediatek/mt7986_eeprom_mt7976_dbdc.bin") case "$board" in asus,tuf-ax4200) diff --git a/mihomo/go.mod b/mihomo/go.mod index f7a63fa75a..494a9a4255 100644 --- a/mihomo/go.mod +++ b/mihomo/go.mod @@ -31,7 +31,7 @@ require ( github.com/metacubex/sing-shadowsocks2 v0.2.6 github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 github.com/metacubex/sing-tun v0.4.8-0.20250827085914-fc5681b9fc9f - github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 + github.com/metacubex/sing-vmess v0.2.4-0.20250828081059-57e77685eef9 github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f github.com/metacubex/smux v0.0.0-20250503055512-501391591dee github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617 diff --git a/mihomo/go.sum b/mihomo/go.sum index 9b62944f4c..3d3d82b1a2 100644 --- a/mihomo/go.sum +++ b/mihomo/go.sum @@ -131,8 +131,8 @@ github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MY github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= github.com/metacubex/sing-tun v0.4.8-0.20250827085914-fc5681b9fc9f h1:1MV/pFn2vjnyvH/0u6sJST0kmaoZXgbUytCCfuelhl8= github.com/metacubex/sing-tun v0.4.8-0.20250827085914-fc5681b9fc9f/go.mod h1:FQ9zXA+kVhdzqgFqeJdi/AUhJgUgw+SUXqrR++GvbnM= -github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 h1:WZepq4TOZa6WewB8tGAZrrL+bL2R2ivoBzuEgAeolWc= -github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= +github.com/metacubex/sing-vmess v0.2.4-0.20250828081059-57e77685eef9 h1:VP7rBmRJUqBpP8uJQpzEqCgnbAYWbz2QtWqoBdNrmwU= +github.com/metacubex/sing-vmess v0.2.4-0.20250828081059-57e77685eef9/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo= diff --git a/mihomo/transport/vless/encryption/client.go b/mihomo/transport/vless/encryption/client.go index 465f16260f..8debb06f64 100644 --- a/mihomo/transport/vless/encryption/client.go +++ b/mihomo/transport/vless/encryption/client.go @@ -65,7 +65,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { if i.NfsPKeys == nil { return nil, errors.New("uninitialized") } - c := &CommonConn{Conn: conn} + c := NewCommonConn(conn) ivAndRealysLength := 16 + i.RelaysLength pfsKeyExchangeLength := 18 + 1184 + 32 + 16 diff --git a/mihomo/transport/vless/encryption/common.go b/mihomo/transport/vless/encryption/common.go index 1b2aa18201..0275a21fa3 100644 --- a/mihomo/transport/vless/encryption/common.go +++ b/mihomo/transport/vless/encryption/common.go @@ -4,16 +4,16 @@ import ( "bytes" "crypto/aes" "crypto/cipher" - "crypto/rand" "errors" "fmt" "io" - "math/big" "net" - "strings" "time" + "github.com/metacubex/mihomo/common/pool" + "github.com/metacubex/blake3" + "github.com/metacubex/randv2" ) type CommonConn struct { @@ -23,36 +23,44 @@ type CommonConn struct { PreWrite []byte GCM *GCM PeerPadding []byte + rawInput bytes.Buffer // PeerInBytes PeerGCM *GCM input bytes.Reader // PeerCache } +func NewCommonConn(conn net.Conn) *CommonConn { + return &CommonConn{ + Conn: conn, + } +} + func (c *CommonConn) Write(b []byte) (int, error) { if len(b) == 0 { return 0, nil } - var data []byte + outBytes := pool.Get(5 + 8192 + 16) + defer pool.Put(outBytes) for n := 0; n < len(b); { b := b[n:] if len(b) > 8192 { b = b[:8192] // for avoiding another copy() in peer's Read() } n += len(b) - data = make([]byte, 5+len(b)+16) - EncodeHeader(data, len(b)+16) + headerAndData := outBytes[:5+len(b)+16] + EncodeHeader(headerAndData, len(b)+16) max := false if bytes.Equal(c.GCM.Nonce[:], MaxNonce) { max = true } - c.GCM.Seal(data[:5], nil, b, data[:5]) + c.GCM.Seal(headerAndData[:5], nil, b, headerAndData[:5]) if max { - c.GCM = NewGCM(data[5:], c.UnitedKey) + c.GCM = NewGCM(headerAndData, c.UnitedKey) } if c.PreWrite != nil { - data = append(c.PreWrite, data...) + headerAndData = append(c.PreWrite, headerAndData...) c.PreWrite = nil } - if _, err := c.Conn.Write(data); err != nil { + if _, err := c.Conn.Write(headerAndData); err != nil { return 0, err } } @@ -85,9 +93,13 @@ func (c *CommonConn) Read(b []byte) (int, error) { if c.input.Len() > 0 { return c.input.Read(b) } - h, l, err := ReadAndDecodeHeader(c.Conn) // l: 17~17000 + peerHeader := make([]byte, 5) + if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { + return 0, err + } + l, err := DecodeHeader(peerHeader) // l: 17~17000 if err != nil { - if c.Client != nil && strings.HasPrefix(err.Error(), "invalid header: ") { // client's 0-RTT + if c.Client != nil && errors.Is(err, ErrInvalidHeader) { // client's 0-RTT c.Client.RWLock.Lock() if bytes.HasPrefix(c.UnitedKey, c.Client.PfsKey) { c.Client.Expire = time.Now() // expired @@ -98,7 +110,8 @@ func (c *CommonConn) Read(b []byte) (int, error) { return 0, err } c.Client = nil - peerData := make([]byte, l) + c.rawInput.Grow(l) + peerData := c.rawInput.Bytes()[:l] if _, err := io.ReadFull(c.Conn, peerData); err != nil { return 0, err } @@ -108,9 +121,9 @@ func (c *CommonConn) Read(b []byte) (int, error) { } var newGCM *GCM if bytes.Equal(c.PeerGCM.Nonce[:], MaxNonce) { - newGCM = NewGCM(peerData, c.UnitedKey) + newGCM = NewGCM(append(peerHeader, peerData...), c.UnitedKey) } - _, err = c.PeerGCM.Open(dst[:0], nil, peerData, h) + _, err = c.PeerGCM.Open(dst[:0], nil, peerData, peerHeader) if newGCM != nil { c.PeerGCM = newGCM } @@ -180,41 +193,22 @@ func EncodeHeader(h []byte, l int) { h[4] = byte(l) } +var ErrInvalidHeader = errors.New("invalid header") + func DecodeHeader(h []byte) (l int, err error) { l = int(h[3])<<8 | int(h[4]) if h[0] != 23 || h[1] != 3 || h[2] != 3 { l = 0 } if l < 17 || l > 17000 { // TODO: TLSv1.3 max length - err = fmt.Errorf("invalid header: %v", h[:5]) // DO NOT CHANGE: relied by client's Read() + err = fmt.Errorf("%w: %v", ErrInvalidHeader, h[:5]) // DO NOT CHANGE: relied by client's Read() } return } -func ReadAndDecodeHeader(conn net.Conn) (h []byte, l int, err error) { - h = make([]byte, 5) - if _, err = io.ReadFull(conn, h); err != nil { - return - } - l, err = DecodeHeader(h) - return -} - -func ReadAndDiscardPaddings(conn net.Conn) (h []byte, l int, err error) { - for { - if h, l, err = ReadAndDecodeHeader(conn); err != nil { - return - } - if _, err = io.ReadFull(conn, make([]byte, l)); err != nil { - return - } - } -} - func randBetween(from int64, to int64) int64 { if from == to { return from } - bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from)) - return from + bigInt.Int64() + return from + randv2.Int64N(to-from) } diff --git a/mihomo/transport/vless/encryption/doc.go b/mihomo/transport/vless/encryption/doc.go index 177839c64a..d3d72e5ad7 100644 --- a/mihomo/transport/vless/encryption/doc.go +++ b/mihomo/transport/vless/encryption/doc.go @@ -20,4 +20,5 @@ // https://github.com/XTLS/Xray-core/commit/ad7140641c44239c9dcdc3d7215ea639b1f0841c // https://github.com/XTLS/Xray-core/commit/0199dea39988a1a1b846d0bf8598631bade40902 // https://github.com/XTLS/Xray-core/commit/fce1195b60f48ca18a953dbd5c7d991869de9a5e +// https://github.com/XTLS/Xray-core/commit/b0b220985c9c1bc832665458d5fd6e0c287b67ae package encryption diff --git a/mihomo/transport/vless/encryption/server.go b/mihomo/transport/vless/encryption/server.go index 36ce399ab5..14ffd1e503 100644 --- a/mihomo/transport/vless/encryption/server.go +++ b/mihomo/transport/vless/encryption/server.go @@ -101,7 +101,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { if i.NfsSKeys == nil { return nil, errors.New("uninitialized") } - c := &CommonConn{Conn: conn} + c := NewCommonConn(conn) ivAndRelays := make([]byte, 16+i.RelaysLength) if _, err := io.ReadFull(conn, ivAndRelays); err != nil { diff --git a/mihomo/transport/vless/vision/conn.go b/mihomo/transport/vless/vision/conn.go index 0c300e05d1..7e778cf84c 100644 --- a/mihomo/transport/vless/vision/conn.go +++ b/mihomo/transport/vless/vision/conn.go @@ -125,6 +125,7 @@ func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { } if vc.input.Len() == 0 { needReturn = true + *vc.input = bytes.Reader{} // full reset vc.input = nil } else { // buffer is full return nil @@ -139,6 +140,7 @@ func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { } needReturn = true if vc.rawInput.Len() == 0 { + *vc.rawInput = bytes.Buffer{} // full reset vc.rawInput = nil } } diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/rule_update.lua b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/rule_update.lua index 8764fe21bb..f447280208 100755 --- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/rule_update.lua +++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/rule_update.lua @@ -95,7 +95,7 @@ end -- curl local function curl(url, file, valifile) local args = { - "-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3" + "-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3", "--max-time 300", "--speed-limit 51200 --speed-time 15" } if file then args[#args + 1] = "-o " .. file @@ -126,7 +126,8 @@ end local function non_file_check(file_path, vali_file) if fs.readfile(file_path, 10) then - local remote_file_size = tonumber(sys.exec("cat " .. vali_file .. " | grep -i 'Content-Length' | awk '{print $2}'")) + local size_str = sys.exec("grep -i 'Content-Length' " .. vali_file .. " | tail -n1 | sed 's/[^0-9]//g'") + local remote_file_size = tonumber(size_str ~= "" and size_str or nil) local local_file_size = tonumber(fs.stat(file_path, "size")) if remote_file_size and local_file_size then if remote_file_size == local_file_size then @@ -317,6 +318,7 @@ local function fetch_geofile(geo_name, geo_type, url) local down_filename = url:match("^.*/([^/?#]+)") local sha_url = url:gsub(down_filename, down_filename .. ".sha256sum") local sha_path = tmp_path .. ".sha256sum" + local vali_file = tmp_path .. ".vali" local function verify_sha256(sha_file) return sys.call("sha256sum -c " .. sha_file .. " > /dev/null 2>&1") == 0 @@ -346,7 +348,18 @@ local function fetch_geofile(geo_name, geo_type, url) end end - if curl(url, tmp_path) == 200 then + local sret_tmp = curl(url, tmp_path, vali_file) + if sret_tmp == 200 and non_file_check(tmp_path, vali_file) then + log(geo_type .. " 下载文件过程出错,尝试重新下载。") + os.remove(tmp_path) + os.remove(vali_file) + sret_tmp = curl(url, tmp_path, vali_file) + if sret_tmp == 200 and non_file_check(tmp_path, vali_file) then + sret_tmp = 0 + log(geo_type .. " 下载文件过程出错,请检查网络或下载链接后重试!") + end + end + if sret_tmp == 200 then if sha_verify then if verify_sha256(sha_path) then sys.call(string.format("mkdir -p %s && cp -f %s %s", asset_location, tmp_path, asset_path)) @@ -451,6 +464,7 @@ end local function remove_tmp_geofile(name) os.remove("/tmp/" .. name .. ".dat") os.remove("/tmp/" .. name .. ".dat.sha256sum") + os.remove("/tmp/" .. name .. ".dat.vali") end if geo2rule == "1" then diff --git a/shadowsocks-rust/Cargo.lock b/shadowsocks-rust/Cargo.lock index 037f897a91..08a074812e 100644 --- a/shadowsocks-rust/Cargo.lock +++ b/shadowsocks-rust/Cargo.lock @@ -3393,6 +3393,7 @@ dependencies = [ "time", "tokio", "tracing", + "tracing-appender", "tracing-subscriber", "windows-service", "xdg", @@ -3973,6 +3974,18 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror 1.0.69", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.30" diff --git a/shadowsocks-rust/Cargo.toml b/shadowsocks-rust/Cargo.toml index 7ccc0bbac5..37aeccb555 100644 --- a/shadowsocks-rust/Cargo.toml +++ b/shadowsocks-rust/Cargo.toml @@ -124,7 +124,7 @@ dns-over-https = ["shadowsocks-service/dns-over-https"] dns-over-h3 = ["shadowsocks-service/dns-over-h3"] # Enable logging output -logging = ["log4rs", "tracing", "tracing-subscriber", "time"] +logging = ["log4rs", "tracing", "tracing-subscriber", "time", "tracing-appender"] # Enable DNS-relay local-dns = ["local", "shadowsocks-service/local-dns"] @@ -208,6 +208,7 @@ tracing-subscriber = { version = "0.3", optional = true, features = [ "time", "local-time", ] } +tracing-appender = { version = "0.2.3", optional = true, default-features = false } time = { version = "0.3", optional = true } serde = { version = "1.0", features = ["derive"] } diff --git a/shadowsocks-rust/README.md b/shadowsocks-rust/README.md index 2a43d3705a..6804f04150 100644 --- a/shadowsocks-rust/README.md +++ b/shadowsocks-rust/README.md @@ -871,19 +871,54 @@ Example configuration: // Service configurations // Logger configuration "log": { + // Default log level to use, if not overridden by `writers`, default is `0` // Equivalent to `-v` command line option "level": 1, + // Default log format to use, if not overridden by `writers` "format": { - // Euiqvalent to `--log-without-time` + // Euiqvalent to `--log-without-time`, default is `false` "without_time": false, }, - // Equivalent to `--log-config` - // More detail could be found in https://crates.io/crates/log4rs - "config_path": "/path/to/log4rs/config.yaml" + // Advanced logging configuration for configuring multiple writers + // A stdout writer will be configured by default. + // Set this to empty array `[]` to disable logging completely + "writers": [ + { + // Configure a stdout writer + // The inner fields are optional, if not set, it will use the default values + // To minimally configure a stdout writer, simply write `"stdout": {}`. + "stdout": { + "level": 2, + "format": { + "without_time": false, + } + } + }, + { + // Configure a file writer, useful when running as a Windows Service + "file": { + // `level` and `format` can also be set here, if not set, it will use the default values + + // Required. Directory to store log files + "directory": "/var/log/shadowsocks-rust", + // Optional. Log rotation frequency, must be one of the following: + // - never (default): This will result in log file located at `directory/prefix.suffix` + // - daily: A new log file in the format of `directory/prefix.yyyy-MM-dd.suffix` will be created daily + // - hourly: A new log file in the format of `directory/prefix.yyyy-MM-dd-HH.suffix` will be created hourly + "rotation": "never", + // Optional. Prefix of log file, default is one of `sslocal`, `ssserver`, `ssmanager` depending on the service being run. + "prefix": "shadowsocks-rust", + // Optional. Suffix of log file, default is `log` + "suffix": "log", + // Optional. If set, keeps the last N log files + "max_files": 5 + } + } + ] }, // Runtime configuration "runtime": { - // single_thread or multi_thread + // `single_thread` or `multi_thread` "mode": "multi_thread", // Worker threads that are used in multi-thread runtime "worker_count": 10 diff --git a/shadowsocks-rust/src/config.rs b/shadowsocks-rust/src/config.rs index 1fef165510..d8f89da711 100644 --- a/shadowsocks-rust/src/config.rs +++ b/shadowsocks-rust/src/config.rs @@ -5,7 +5,6 @@ use std::{ fs::OpenOptions, io::{self, Read}, path::{Path, PathBuf}, - str::FromStr, }; use clap::ArgMatches; @@ -96,7 +95,8 @@ pub enum ConfigError { } /// Configuration Options for shadowsocks service runnables -#[derive(Debug, Clone, Default)] +#[derive(Deserialize, Debug, Clone, Default)] +#[serde(default)] pub struct Config { /// Logger configuration #[cfg(feature = "logging")] @@ -120,54 +120,7 @@ impl Config { /// Load `Config` from string pub fn load_from_str(s: &str) -> Result { - let ssconfig = json5::from_str(s)?; - Self::load_from_ssconfig(ssconfig) - } - - fn load_from_ssconfig(ssconfig: SSConfig) -> Result { - let mut config = Self::default(); - - #[cfg(feature = "logging")] - if let Some(log) = ssconfig.log { - let mut nlog = LogConfig::default(); - if let Some(level) = log.level { - nlog.level = level; - } - - if let Some(format) = log.format { - let mut nformat = LogFormatConfig::default(); - if let Some(without_time) = format.without_time { - nformat.without_time = without_time; - } - nlog.format = nformat; - } - - if let Some(config_path) = log.config_path { - nlog.config_path = Some(PathBuf::from(config_path)); - } - - config.log = nlog; - } - - if let Some(runtime) = ssconfig.runtime { - let mut nruntime = RuntimeConfig::default(); - - #[cfg(feature = "multi-threaded")] - if let Some(worker_count) = runtime.worker_count { - nruntime.worker_count = Some(worker_count); - } - - if let Some(mode) = runtime.mode { - match mode.parse::() { - Ok(m) => nruntime.mode = m, - Err(..) => return Err(ConfigError::InvalidValue(mode)), - } - } - - config.runtime = nruntime; - } - - Ok(config) + json5::from_str(s).map_err(ConfigError::from) } /// Set by command line options @@ -198,31 +151,127 @@ impl Config { self.runtime.worker_count = Some(*worker_count); } + // suppress unused warning let _ = matches; } } /// Logger configuration #[cfg(feature = "logging")] -#[derive(Debug, Clone, Default)] +#[derive(Deserialize, Debug, Clone)] +#[serde(default)] pub struct LogConfig { - /// Default logger log level, [0, 3] + /// Default log level for all writers, [0, 3] pub level: u32, - /// Default logger format configuration + /// Default format configuration for all writers pub format: LogFormatConfig, - /// Logging configuration file path + /// Log writers configuration + pub writers: Vec, + /// Deprecated: Path to the `log4rs` config file pub config_path: Option, } +#[cfg(feature = "logging")] +impl Default for LogConfig { + fn default() -> Self { + LogConfig { + level: 0, + format: LogFormatConfig::default(), + writers: vec![LogWriterConfig::Console(LogConsoleWriterConfig::default())], + config_path: None, + } + } +} + /// Logger format configuration #[cfg(feature = "logging")] -#[derive(Debug, Clone, Default)] +#[derive(Deserialize, Debug, Clone, Default, Eq, PartialEq)] +#[serde(default)] pub struct LogFormatConfig { pub without_time: bool, } +/// Holds writer-specific configuration for logging +#[cfg(feature = "logging")] +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum LogWriterConfig { + Console(LogConsoleWriterConfig), + File(LogFileWriterConfig), +} + +/// Console appender configuration for logging +#[cfg(feature = "logging")] +#[derive(Deserialize, Debug, Clone, Default)] +pub struct LogConsoleWriterConfig { + /// Level override + #[serde(default)] + pub level: Option, + /// Format override + #[serde(default)] + pub format: LogFormatConfigOverride, +} + +/// Logger format override +#[cfg(feature = "logging")] +#[derive(Deserialize, Debug, Clone, Default)] +#[serde(default)] +pub struct LogFormatConfigOverride { + pub without_time: Option, +} + +/// File appender configuration for logging +#[cfg(feature = "logging")] +#[derive(Deserialize, Debug, Clone)] +pub struct LogFileWriterConfig { + /// Level override + #[serde(default)] + pub level: Option, + /// Format override + #[serde(default)] + pub format: LogFormatConfigOverride, + + /// Directory to store log files + pub directory: PathBuf, + /// Rotation strategy for log files. Default is `Rotation::NEVER`. + #[serde(default)] + pub rotation: LogRotation, + /// Prefix for log file names. Default is the binary name. + #[serde(default)] + pub prefix: Option, + /// Suffix for log file names. Default is "log". + #[serde(default)] + pub suffix: Option, + /// Maximum number of log files to keep. Default is `None`, meaning no limit. + #[serde(default)] + pub max_files: Option, +} + +/// Log rotation frequency +#[cfg(feature = "logging")] +#[derive(Deserialize, Debug, Copy, Clone, Default, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum LogRotation { + #[default] + Never, + Hourly, + Daily, +} + +#[cfg(feature = "logging")] +impl From for tracing_appender::rolling::Rotation { + fn from(rotation: LogRotation) -> Self { + match rotation { + LogRotation::Never => Self::NEVER, + LogRotation::Hourly => Self::HOURLY, + LogRotation::Daily => Self::DAILY, + } + } +} + /// Runtime mode (Tokio) -#[derive(Debug, Clone, Copy, Default)] +#[derive(Deserialize, Debug, Clone, Copy, Default, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] pub enum RuntimeMode { /// Single-Thread Runtime #[cfg_attr(not(feature = "multi-threaded"), default)] @@ -233,25 +282,9 @@ pub enum RuntimeMode { MultiThread, } -/// Parse `RuntimeMode` from string error -#[derive(Debug)] -pub struct RuntimeModeError; - -impl FromStr for RuntimeMode { - type Err = RuntimeModeError; - - fn from_str(s: &str) -> Result { - match s { - "single_thread" => Ok(Self::SingleThread), - #[cfg(feature = "multi-threaded")] - "multi_thread" => Ok(Self::MultiThread), - _ => Err(RuntimeModeError), - } - } -} - /// Runtime configuration -#[derive(Debug, Clone, Default)] +#[derive(Deserialize, Debug, Clone, Default)] +#[serde(default)] pub struct RuntimeConfig { /// Multithread runtime worker count, CPU count if not configured #[cfg(feature = "multi-threaded")] @@ -260,30 +293,182 @@ pub struct RuntimeConfig { pub mode: RuntimeMode, } -#[derive(Deserialize)] -struct SSConfig { - #[cfg(feature = "logging")] - log: Option, - runtime: Option, -} +#[cfg(test)] +mod tests { + use super::*; -#[cfg(feature = "logging")] -#[derive(Deserialize)] -struct SSLogConfig { - level: Option, - format: Option, - config_path: Option, -} + #[test] + fn test_deser_empty() { + // empty config should load successfully + let config: Config = Config::load_from_str("{}").unwrap(); + assert_eq!(config.runtime.mode, RuntimeMode::default()); + #[cfg(feature = "multi-threaded")] + { + assert!(config.runtime.worker_count.is_none()); + } + #[cfg(feature = "logging")] + { + assert_eq!(config.log.level, 0); + assert!(!config.log.format.without_time); + // default writer configuration should contain a stdout writer + assert_eq!(config.log.writers.len(), 1); + if let LogWriterConfig::Console(stdout_config) = &config.log.writers[0] { + assert_eq!(stdout_config.level, None); + assert_eq!(stdout_config.format.without_time, None); + } else { + panic!("Expected a stdout writer configuration"); + } + } + } -#[cfg(feature = "logging")] -#[derive(Deserialize)] -struct SSLogFormat { - without_time: Option, -} + #[test] + fn test_deser_disable_logging() { + // allow user explicitly disable logging by providing an empty writers array + let config_str = r#" + { + "log": { + "writers": [] + } + } + "#; + let config: Config = Config::load_from_str(config_str).unwrap(); + #[cfg(feature = "logging")] + { + assert_eq!(config.log.level, 0); + assert!(!config.log.format.without_time); + assert!(config.log.writers.is_empty()); + } + } -#[derive(Deserialize)] -struct SSRuntimeConfig { - #[cfg(feature = "multi-threaded")] - worker_count: Option, - mode: Option, + #[test] + fn test_deser_file_writer_full() { + let config_str = r#" + { + "log": { + "writers": [ + { + "file": { + "level": 2, + "format": { + "without_time": true + }, + "directory": "/var/log/shadowsocks", + "rotation": "daily", + "prefix": "ss-rust", + "suffix": "log", + "max_files": 5 + } + } + ] + } + } + "#; + let config: Config = Config::load_from_str(config_str).unwrap(); + #[cfg(feature = "logging")] + { + assert_eq!(config.log.writers.len(), 1); + if let LogWriterConfig::File(file_config) = &config.log.writers[0] { + assert_eq!(file_config.level, Some(2)); + assert_eq!(file_config.format.without_time, Some(true)); + assert_eq!(file_config.directory, PathBuf::from("/var/log/shadowsocks")); + assert_eq!(file_config.rotation, LogRotation::Daily); + assert_eq!(file_config.prefix.as_deref(), Some("ss-rust")); + assert_eq!(file_config.suffix.as_deref(), Some("log")); + assert_eq!(file_config.max_files, Some(5)); + } else { + panic!("Expected a file writer configuration"); + } + } + } + + #[test] + fn test_deser_file_writer_minimal() { + // Minimal valid file writer configuration + let config_str = r#" + { + "log": { + "writers": [ + { + "file": { + "directory": "/var/log/shadowsocks" + } + } + ] + } + } + "#; + let config: Config = Config::load_from_str(config_str).unwrap(); + #[cfg(feature = "logging")] + { + assert_eq!(config.log.writers.len(), 1); + if let LogWriterConfig::File(file_config) = &config.log.writers[0] { + assert_eq!(file_config.level, None); + assert_eq!(file_config.format.without_time, None); + assert_eq!(file_config.directory, PathBuf::from("/var/log/shadowsocks")); + assert_eq!(file_config.rotation, LogRotation::Never); + assert!(file_config.prefix.is_none()); + assert!(file_config.suffix.is_none()); + assert!(file_config.max_files.is_none()); + } else { + panic!("Expected a file writer configuration"); + } + } + } + #[test] + fn test_deser_console_writer_full() { + let config_str = r#" + { + "log": { + "writers": [ + { + "console": { + "level": 1, + "format": { + "without_time": false + } + } + } + ] + } + } + "#; + let config: Config = Config::load_from_str(config_str).unwrap(); + #[cfg(feature = "logging")] + { + assert_eq!(config.log.writers.len(), 1); + if let LogWriterConfig::Console(stdout_config) = &config.log.writers[0] { + assert_eq!(stdout_config.level, Some(1)); + assert_eq!(stdout_config.format.without_time, Some(false)); + } else { + panic!("Expected a console writer configuration"); + } + } + } + + #[test] + fn test_deser_console_writer_minimal() { + // Minimal valid console writer configuration + let config_str = r#" + { + "log": { + "writers": [ + { + "console": {} + } + ] + } + } + "#; + let config: Config = Config::load_from_str(config_str).unwrap(); + #[cfg(feature = "logging")] + { + assert_eq!(config.log.writers.len(), 1); + if let LogWriterConfig::Console(stdout_config) = &config.log.writers[0] { + assert_eq!(stdout_config.level, None); + assert_eq!(stdout_config.format.without_time, None); + } else { + panic!("Expected a console writer configuration"); + } + } + } } diff --git a/shadowsocks-rust/src/logging/mod.rs b/shadowsocks-rust/src/logging/mod.rs index 7f5098ccd6..4a55e74aaa 100644 --- a/shadowsocks-rust/src/logging/mod.rs +++ b/shadowsocks-rust/src/logging/mod.rs @@ -9,7 +9,7 @@ use crate::config::LogConfig; mod log4rs; mod tracing; -/// Initialize logger ([log4rs](https://crates.io/crates/log4rs), [trace4rs](https://crates.io/crates/trace4rs)) from yaml configuration file +/// Initialize [log4rs](https://crates.io/crates/log4rs) from yaml configuration file pub fn init_with_file

(path: P) where P: AsRef, @@ -25,7 +25,6 @@ where /// Initialize logger with provided configuration pub fn init_with_config(bin_name: &str, config: &LogConfig) { - // log4rs::init_with_config(bin_name, config); tracing::init_with_config(bin_name, config); } diff --git a/shadowsocks-rust/src/logging/tracing.rs b/shadowsocks-rust/src/logging/tracing.rs index b045946d71..42a1482dbd 100644 --- a/shadowsocks-rust/src/logging/tracing.rs +++ b/shadowsocks-rust/src/logging/tracing.rs @@ -1,51 +1,112 @@ //! Logging facilities with tracing +use std::io; use std::io::IsTerminal; use time::UtcOffset; +use time::format_description::well_known::Rfc3339; use tracing::level_filters::LevelFilter; -use tracing_subscriber::{EnvFilter, FmtSubscriber, fmt::time::OffsetTime}; +use tracing_appender::rolling::{InitError, RollingFileAppender}; +use tracing_subscriber::fmt::MakeWriter; +use tracing_subscriber::fmt::time::OffsetTime; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{EnvFilter, Layer, Registry, fmt}; -use crate::config::LogConfig; +use crate::config::{ + LogConfig, LogConsoleWriterConfig, LogFileWriterConfig, LogFormatConfig, LogFormatConfigOverride, LogWriterConfig, +}; /// Initialize logger with provided configuration pub fn init_with_config(bin_name: &str, config: &LogConfig) { - let debug_level = config.level; - let without_time = config.format.without_time; + let layers: Vec = config + .writers + .iter() + .map(|writer| writer.make_layer(bin_name, config)) + .collect(); + tracing_subscriber::registry().with(layers).init(); +} - let mut builder = FmtSubscriber::builder() - .with_level(true) - .with_timer(match OffsetTime::local_rfc_3339() { - Ok(t) => t, - Err(..) => { - // Reinit with UTC time - OffsetTime::new(UtcOffset::UTC, time::format_description::well_known::Rfc3339) - } - }); +type BoxedLayer = Box + Send + Sync + 'static>; + +trait MakeLayer { + fn make_layer(&self, bin_name: &str, global: &LogConfig) -> BoxedLayer; +} + +impl MakeLayer for LogWriterConfig { + fn make_layer(&self, bin_name: &str, global: &LogConfig) -> BoxedLayer { + match self { + LogWriterConfig::Console(console_config) => console_config.make_layer(bin_name, global), + LogWriterConfig::File(file_config) => file_config.make_layer(bin_name, global), + } + } +} + +impl MakeLayer for LogConsoleWriterConfig { + fn make_layer(&self, bin_name: &str, global: &LogConfig) -> BoxedLayer { + let level = self.level.unwrap_or(global.level); + let format = apply_override(&global.format, &self.format); + let ansi = io::stdout().is_terminal(); + make_fmt_layer(bin_name, level, &format, ansi, io::stdout) + } +} + +impl MakeLayer for LogFileWriterConfig { + fn make_layer(&self, bin_name: &str, global: &LogConfig) -> BoxedLayer { + let level = self.level.unwrap_or(global.level); + let format = apply_override(&global.format, &self.format); + + let file_writer = make_file_writer(bin_name, self) + // don't have the room for a more graceful error handling here + .expect("Failed to create file writer for logging"); + make_fmt_layer(bin_name, level, &format, false, file_writer) + } +} + +/// Boilerplate for configuring a `fmt::Layer` with `level` and `format` for different writers. +fn make_fmt_layer(bin_name: &str, level: u32, format: &LogFormatConfig, ansi: bool, writer: W) -> BoxedLayer +where + W: for<'a> MakeWriter<'a> + Send + Sync + 'static, +{ + let mut layer = fmt::layer().with_level(true); // NOTE: ansi is enabled by default. // Could be disabled by `NO_COLOR` environment variable. // https://no-color.org/ - if !std::io::stdout().is_terminal() { - builder = builder.with_ansi(false); + if !ansi { + layer = layer.with_ansi(false); } - if debug_level >= 1 { - builder = builder.with_target(true).with_thread_ids(true).with_thread_names(true); + if level >= 1 { + layer = layer.with_target(true).with_thread_ids(true).with_thread_names(true); - if debug_level >= 3 { - builder = builder.with_file(true).with_line_number(true); + if level >= 3 { + layer = layer.with_file(true).with_line_number(true); } } else { - builder = builder - .with_target(false) - .with_thread_ids(false) - .with_thread_names(false); + layer = layer.with_target(false).with_thread_ids(false).with_thread_names(false); } - let filter = match EnvFilter::try_from_default_env() { + let layer = layer.with_writer(writer); + + let boxed_layer = if format.without_time { + layer.without_time().boxed() + } else { + layer + .with_timer(OffsetTime::local_rfc_3339() + // Fallback to UTC. Eagerly evaluate because it is cheap to create. + .unwrap_or(OffsetTime::new(UtcOffset::UTC, Rfc3339))) + .boxed() + }; + + let filter = make_env_filter(bin_name, level); + boxed_layer.with_filter(filter).boxed() +} + +fn make_env_filter(bin_name: &str, level: u32) -> EnvFilter { + match EnvFilter::try_from_default_env() { Ok(f) => f, - Err(..) => match debug_level { + Err(_) => match level { 0 => EnvFilter::builder() .with_regex(true) .with_default_directive(LevelFilter::ERROR.into()) @@ -71,12 +132,33 @@ pub fn init_with_config(bin_name: &str, config: &LogConfig) { .with_default_directive(LevelFilter::TRACE.into()) .parse_lossy(""), }, - }; - let builder = builder.with_env_filter(filter); - - if without_time { - builder.without_time().init(); - } else { - builder.init(); + } +} + +fn make_file_writer(bin_name: &str, config: &LogFileWriterConfig) -> Result { + // We provide default values here because we don't have access to the + // `bin_name` elsewhere. + let prefix = config.prefix.as_deref().unwrap_or(bin_name); + let suffix = config.suffix.as_deref().unwrap_or("log"); + + let mut builder = RollingFileAppender::builder() + .rotation(config.rotation.into()) + .filename_prefix(prefix) + .filename_suffix(suffix); + + if let Some(max_files) = config.max_files { + // setting `max_files` to `0` will cause panicking due to + // integer underflow in the `tracing_appender` crate. + if max_files > 0 { + builder = builder.max_log_files(max_files); + } + } + + builder.build(&config.directory) +} + +fn apply_override(global: &LogFormatConfig, override_config: &LogFormatConfigOverride) -> LogFormatConfig { + LogFormatConfig { + without_time: override_config.without_time.unwrap_or(global.without_time), } } diff --git a/shadowsocks-rust/src/service/local.rs b/shadowsocks-rust/src/service/local.rs index cf01f37a3a..f142e3c47d 100644 --- a/shadowsocks-rust/src/service/local.rs +++ b/shadowsocks-rust/src/service/local.rs @@ -261,6 +261,8 @@ pub fn define_command_line_options(mut app: Command) -> Command { .arg( Arg::new("LOG_CONFIG") .long("log-config") + // deprecated for removal + .hide(true) .num_args(1) .action(ArgAction::Set) .value_parser(clap::value_parser!(PathBuf)) diff --git a/shadowsocks-rust/src/service/manager.rs b/shadowsocks-rust/src/service/manager.rs index ae7f1d5ba0..0eadd30fd4 100644 --- a/shadowsocks-rust/src/service/manager.rs +++ b/shadowsocks-rust/src/service/manager.rs @@ -148,6 +148,8 @@ pub fn define_command_line_options(mut app: Command) -> Command { .arg( Arg::new("LOG_CONFIG") .long("log-config") + // deprecated for removal + .hide(true) .num_args(1) .action(ArgAction::Set) .value_parser(clap::value_parser!(PathBuf)) @@ -297,7 +299,7 @@ pub fn create(matches: &ArgMatches) -> ShadowsocksResult<(Runtime, impl Future { - logging::init_with_config("sslocal", &service_config.log); + logging::init_with_config("ssmanager", &service_config.log); } } diff --git a/shadowsocks-rust/src/service/server.rs b/shadowsocks-rust/src/service/server.rs index 045243aa3a..eef03d8167 100644 --- a/shadowsocks-rust/src/service/server.rs +++ b/shadowsocks-rust/src/service/server.rs @@ -178,6 +178,8 @@ pub fn define_command_line_options(mut app: Command) -> Command { .arg( Arg::new("LOG_CONFIG") .long("log-config") + // deprecated for removal + .hide(true) .num_args(1) .action(ArgAction::Set) .value_parser(clap::value_parser!(PathBuf)) @@ -309,7 +311,7 @@ pub fn create(matches: &ArgMatches) -> ShadowsocksResult<(Runtime, impl Future { - logging::init_with_config("sslocal", &service_config.log); + logging::init_with_config("ssserver", &service_config.log); } } diff --git a/sing-box/clients/android/version.properties b/sing-box/clients/android/version.properties index 9d950133d5..26cd00cbfd 100644 --- a/sing-box/clients/android/version.properties +++ b/sing-box/clients/android/version.properties @@ -1,3 +1,3 @@ -VERSION_CODE=556 -VERSION_NAME=1.12.3 +VERSION_CODE=560 +VERSION_NAME=1.12.4 GO_VERSION=go1.25.0 diff --git a/sing-box/docs/changelog.md b/sing-box/docs/changelog.md index 45be3c1518..0d9d6e1805 100644 --- a/sing-box/docs/changelog.md +++ b/sing-box/docs/changelog.md @@ -2,6 +2,14 @@ icon: material/alert-decagram --- +#### 1.13.0-alpha.8 + +* Fixes and improvements + +#### 1.12.4 + +* Fixes and improvements + #### 1.13.0-alpha.7 * Add reject support for ICMP echo supports **1** diff --git a/sing-box/go.mod b/sing-box/go.mod index e4e3196cba..5f3356567e 100644 --- a/sing-box/go.mod +++ b/sing-box/go.mod @@ -36,7 +36,7 @@ require ( github.com/sagernet/sing-tun v0.7.0-beta.1.0.20250827122908-b76e852f59b0 github.com/sagernet/sing-vmess v0.2.7 github.com/sagernet/smux v1.5.34-mod.2 - github.com/sagernet/tailscale v1.80.3-mod.6 + github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1 github.com/sagernet/wireguard-go v0.0.1-beta.7 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.9.1 diff --git a/sing-box/go.sum b/sing-box/go.sum index 9b50d11d65..0689e99456 100644 --- a/sing-box/go.sum +++ b/sing-box/go.sum @@ -185,8 +185,8 @@ github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiY github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc= -github.com/sagernet/tailscale v1.80.3-mod.6 h1:oJs0jpRNS/12+mPf3r9maxWl9dWy1RanugLNmsF74Gs= -github.com/sagernet/tailscale v1.80.3-mod.6/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= +github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1 h1:cWM1iPwqIE1t06ft80wpvFB4xbhOpIFI+TFnTw2gnbs= +github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI= github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= diff --git a/sing-box/transport/wireguard/device_stack.go b/sing-box/transport/wireguard/device_stack.go index 8b7c40cdef..a190baba4f 100644 --- a/sing-box/transport/wireguard/device_stack.go +++ b/sing-box/transport/wireguard/device_stack.go @@ -110,11 +110,17 @@ func (w *stackDevice) DialContext(ctx context.Context, network string, destinati } var networkProtocol tcpip.NetworkProtocolNumber if destination.IsIPv4() { + if !w.inet4Address.IsValid() { + return nil, E.New("missing IPv4 local address") + } networkProtocol = header.IPv4ProtocolNumber bind.Addr = tun.AddressFromAddr(w.inet4Address) } else { + if !w.inet6Address.IsValid() { + return nil, E.New("missing IPv6 local address") + } networkProtocol = header.IPv6ProtocolNumber - bind.Addr = tun.AddressFromAddr(w.inet4Address) + bind.Addr = tun.AddressFromAddr(w.inet6Address) } switch N.NetworkName(network) { case N.NetworkTCP: diff --git a/small/luci-app-fchomo/Makefile b/small/luci-app-fchomo/Makefile index a1db6fa14d..2de03c3cd8 100644 --- a/small/luci-app-fchomo/Makefile +++ b/small/luci-app-fchomo/Makefile @@ -33,6 +33,27 @@ endef PKG_UNPACK=$(CURDIR)/.prepare.sh $(PKG_NAME) $(CURDIR) $(PKG_BUILD_DIR) +define Package/luci-app-fchomo/postinst +#!/bin/sh +# openwrt version check +export REVISION VERSION_NUMBER ARCH_PACKAGES +if [ -n "$$IPKG_INSTROOT" ]; then + # building + REVISION=$$($$TOPDIR/scripts/getver.sh) + REVISION=$$(echo "$$REVISION" | cut -f1 -d'-' | sed 's|[a-z]||gi') +else + # system + REVISION=$$(ubus call system board | jsonfilter -qe '@.release.revision' | cut -f1 -d'-' | sed 's|[a-z]||gi') +fi + +[ "$$REVISION" -ge 28158 ] || { 2>&1 echo "Minimum OpenWrt version required is 24.10."; exit 1; } +# https://archive.openwrt.org/releases/**/version.buildinfo +# r?????-?????????? 25.??.?-rc1 +# r28158-d276b4c91a 24.10.0-rc1 +# r23069-e2701e0f33 23.05.0-rc1 +exit 0 +endef + define Package/luci-app-fchomo/prerm #!/bin/sh uci delete firewall.fchomo_pre diff --git a/small/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js b/small/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js index cfdc2e2562..459a108dd3 100644 --- a/small/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js +++ b/small/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js @@ -20,8 +20,6 @@ const sharktaikogif = function() { 'c2hhcmstdGFpa28uZ2lm' }() -const less_24_10 = !form.RichListValue; - const pr7558_merged = form.DynamicList.prototype.renderWidget.toString().match('this\.allowduplicates'); const monospacefonts = [ @@ -354,7 +352,7 @@ const CBIGridSection = form.GridSection.extend({ } }); -const CBIDynamicList = form.DynamicList.extend({ +const CBIDynamicList = form.DynamicList.extend({ // @pr7558_merged __name__: 'CBI.DynamicList', renderWidget(section_id, option_index, cfgvalue) { @@ -390,14 +388,14 @@ const CBIListValue = form.ListValue.extend({ const CBIRichMultiValue = form.MultiValue.extend({ __name__: 'CBI.RichMultiValue', - value: (form.RichListValue || form.MultiValue).prototype.value // @less_24_10 + value: form.RichListValue.prototype.value }); const CBIStaticList = form.DynamicList.extend({ __name__: 'CBI.StaticList', renderWidget(/* ... */) { - let El = ((less_24_10 || !pr7558_merged) ? CBIDynamicList : form.DynamicList).prototype.renderWidget.apply(this, arguments); + let El = (!pr7558_merged ? CBIDynamicList : form.DynamicList).prototype.renderWidget.apply(this, arguments); // @pr7558_merged El.querySelector('.add-item ul > li[data-value="-"]')?.remove(); @@ -574,10 +572,10 @@ const CBIHandleImport = baseclass.extend(/** @lends hm.HandleImport.prototype */ } }); -const UIDynamicList = ui.DynamicList.extend({ +const UIDynamicList = ui.DynamicList.extend({ // @pr7558_merged addItem(dl, value, text, flash) { if (this.options.allowduplicates) { - const new_item = E('div', { class: flash ? 'item flash' : 'item', tabindex: 0, draggable: !less_24_10 }, [ + const new_item = E('div', { class: flash ? 'item flash' : 'item', tabindex: 0, draggable: true }, [ E('span', {}, [ text ?? value ]), E('input', { type: 'hidden', @@ -1003,7 +1001,7 @@ function handleGenKey(option) { return callMihomoGenerator(option.type, option.params).then((ret) => { if (ret.result) for (let key in option.result) - widget(option.result[key]).value = ret.result[key] || ''; + widget(option.result[key]).value = ret.result[key] ?? ''; else ui.addNotification(null, E('p', _('Failed to generate %s, error: %s.').format(type, ret.error))); }); @@ -1393,7 +1391,6 @@ return baseclass.extend({ rulesetdoc, sharkaudio, sharktaikogif, - less_24_10, pr7558_merged, monospacefonts, checkurls, diff --git a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/client.js b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/client.js index 86af03956f..9e8ae82d68 100644 --- a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/client.js +++ b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/client.js @@ -594,7 +594,7 @@ function renderPayload(s, total, uciconfig) { return true; } - o = s.option((hm.less_24_10 || !hm.pr7558_merged) ? hm.DynamicList : form.DynamicList, prefix + 'fused', _('Factor') + ' ++', + o = s.option(!hm.pr7558_merged ? hm.DynamicList : form.DynamicList, prefix + 'fused', _('Factor') + ' ++', // @pr7558_merged _('Content will not be verified, Please make sure you enter it correctly.')); extenbox[n].forEach((type) => { o.depends(Object.fromEntries([['type', type], [prefix + 'type', /.+/]])); diff --git a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js index f5737ac6d6..5fb33e82c0 100644 --- a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js +++ b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js @@ -491,7 +491,7 @@ return view.extend({ o = s.taboption('inbound', form.SectionValue, '_inbound', form.NamedSection, 'inbound', 'fchomo', _('Tun settings')); ss = o.subsection; - so = ss.option(form.RichListValue || form.ListValue, 'tun_stack', _('Stack'), // @less_24_10 + so = ss.option(form.RichListValue, 'tun_stack', _('Stack'), _('Tun stack.')); so.value('system', _('System'), _('Less compatibility and sometimes better performance.')); if (features.with_gvisor) { @@ -500,16 +500,6 @@ return view.extend({ } so.default = 'system'; so.rmempty = false; - if (hm.less_24_10) - so.onchange = function(ev, section_id, value) { - let desc = ev.target.nextSibling; - if (value === 'mixed') - desc.innerHTML = _('Mixed system TCP stack and gVisor UDP stack.'); - else if (value === 'gvisor') - desc.innerHTML = _('Based on google/gvisor.'); - else if (value === 'system') - desc.innerHTML = _('Less compatibility and sometimes better performance.'); - } so = ss.option(form.Value, 'tun_mtu', _('MTU')); so.datatype = 'uinteger'; diff --git a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js index 05bf1bef11..4a18fc52c6 100644 --- a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js +++ b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js @@ -220,11 +220,18 @@ return view.extend({ so.modalonly = true; so = ss.taboption('field_general', form.ListValue, 'mieru_multiplexing', _('Multiplexing')); + so.default = 'MULTIPLEXING_LOW'; so.value('MULTIPLEXING_OFF'); so.value('MULTIPLEXING_LOW'); so.value('MULTIPLEXING_MIDDLE'); so.value('MULTIPLEXING_HIGH'); - so.default = 'MULTIPLEXING_LOW'; + so.depends('type', 'mieru'); + so.modalonly = true; + + so = ss.taboption('field_general', form.ListValue, 'mieru_handshake_mode', _('Handshake mode')); + so.default = 'HANDSHAKE_STANDARD'; + so.value('HANDSHAKE_STANDARD'); + so.value('HANDSHAKE_NO_WAIT'); so.depends('type', 'mieru'); so.modalonly = true; @@ -268,6 +275,7 @@ return view.extend({ so = ss.taboption('field_general', form.ListValue, 'tuic_udp_relay_mode', _('UDP relay mode'), _('UDP packet relay mode.')); + so.default = 'native'; so.value('native', _('Native')); so.value('quic', _('QUIC')); so.depends({type: 'tuic', tuic_udp_over_stream: '0'}); @@ -411,6 +419,10 @@ return view.extend({ so.depends({type: /^(vmess|vless)$/}); so.modalonly = true; + so = ss.taboption('field_general', form.Value, 'vless_encryption', _('encryption')); + so.depends('type', 'vless'); + so.modalonly = true; + /* WireGuard fields */ so = ss.taboption('field_general', form.Value, 'wireguard_ip', _('Local address'), _('The %s address used by local machine in the Wireguard network.').format('IPv4')); diff --git a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/server.js b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/server.js index 64bf6580e6..055b84ef83 100644 --- a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/server.js +++ b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/server.js @@ -261,6 +261,10 @@ return view.extend({ o.depends('type', 'vmess'); o.modalonly = true; + o = s.taboption('field_general', form.Value, 'vless_decryption', _('decryption')); + o.depends('type', 'vless'); + o.modalonly = true; + /* Plugin fields */ o = s.taboption('field_general', form.ListValue, 'plugin', _('Plugin')); o.value('', _('none')); diff --git a/small/luci-app-fchomo/po/templates/fchomo.pot b/small/luci-app-fchomo/po/templates/fchomo.pot index 1e6ffb1695..f84ac28e80 100644 --- a/small/luci-app-fchomo/po/templates/fchomo.pot +++ b/small/luci-app-fchomo/po/templates/fchomo.pot @@ -1039,6 +1039,10 @@ msgstr "" msgid "Handle domain" msgstr "" +#: htdocs/luci-static/resources/view/fchomo/node.js:231 +msgid "Handshake mode" +msgstr "" + #: htdocs/luci-static/resources/view/fchomo/server.js:271 msgid "Handshake target that supports TLS 1.3" msgstr "" diff --git a/small/luci-app-fchomo/po/zh_Hans/fchomo.po b/small/luci-app-fchomo/po/zh_Hans/fchomo.po index 8992033384..e157a5735f 100644 --- a/small/luci-app-fchomo/po/zh_Hans/fchomo.po +++ b/small/luci-app-fchomo/po/zh_Hans/fchomo.po @@ -1061,6 +1061,10 @@ msgstr "" msgid "Handle domain" msgstr "处理域名" +#: htdocs/luci-static/resources/view/fchomo/node.js:231 +msgid "Handshake mode" +msgstr "握手模式" + #: htdocs/luci-static/resources/view/fchomo/server.js:271 msgid "Handshake target that supports TLS 1.3" msgstr "握手目标 (支援 TLS 1.3)" diff --git a/small/luci-app-fchomo/po/zh_Hant/fchomo.po b/small/luci-app-fchomo/po/zh_Hant/fchomo.po index a49ec88e2c..cf48f58121 100644 --- a/small/luci-app-fchomo/po/zh_Hant/fchomo.po +++ b/small/luci-app-fchomo/po/zh_Hant/fchomo.po @@ -1061,6 +1061,10 @@ msgstr "" msgid "Handle domain" msgstr "處理網域" +#: htdocs/luci-static/resources/view/fchomo/node.js:231 +msgid "Handshake mode" +msgstr "握手模式" + #: htdocs/luci-static/resources/view/fchomo/server.js:271 msgid "Handshake target that supports TLS 1.3" msgstr "握手目標 (支援 TLS 1.3)" diff --git a/small/luci-app-fchomo/root/usr/share/fchomo/generate_client.uc b/small/luci-app-fchomo/root/usr/share/fchomo/generate_client.uc index 73da9515b5..c8d87048c4 100644 --- a/small/luci-app-fchomo/root/usr/share/fchomo/generate_client.uc +++ b/small/luci-app-fchomo/root/usr/share/fchomo/generate_client.uc @@ -510,6 +510,7 @@ uci.foreach(uciconf, ucinode, (cfg) => { "port-range": cfg.mieru_port_range, transport: cfg.mieru_transport, multiplexing: cfg.mieru_multiplexing, + "handshake-mode": cfg.mieru_handshake_mode, /* Snell */ psk: cfg.snell_psk, @@ -550,6 +551,7 @@ uci.foreach(uciconf, ucinode, (cfg) => { "global-padding": cfg.type === 'vmess' ? (cfg.vmess_global_padding === '0' ? false : true) : null, "authenticated-length": strToBool(cfg.vmess_authenticated_length), "packet-encoding": cfg.vmess_packet_encoding, + encryption: cfg.vless_encryption, /* WireGuard */ ip: cfg.wireguard_ip, diff --git a/small/luci-app-fchomo/root/usr/share/fchomo/generate_server.uc b/small/luci-app-fchomo/root/usr/share/fchomo/generate_server.uc index 2034d23347..7a45b15bdc 100644 --- a/small/luci-app-fchomo/root/usr/share/fchomo/generate_server.uc +++ b/small/luci-app-fchomo/root/usr/share/fchomo/generate_server.uc @@ -102,6 +102,9 @@ uci.foreach(uciconf, uciserver, (cfg) => { /* AnyTLS */ "padding-scheme": cfg.anytls_padding_scheme, + /* VMess / VLESS */ + decryption: cfg.vless_decryption, + /* Plugin fields */ ...(cfg.plugin ? { // shadow-tls diff --git a/small/luci-app-fchomo/root/usr/share/rpcd/ucode/luci.fchomo b/small/luci-app-fchomo/root/usr/share/rpcd/ucode/luci.fchomo index a47bff36ed..38671e95b1 100644 --- a/small/luci-app-fchomo/root/usr/share/rpcd/ucode/luci.fchomo +++ b/small/luci-app-fchomo/root/usr/share/rpcd/ucode/luci.fchomo @@ -43,7 +43,7 @@ const methods = { mihomo_generator: { args: { type: 'type', params: 'params' }, call: function(req) { - if (!(req.args?.type in ['uuid', 'reality-keypair', 'wg-keypair', 'ech-keypair'])) + if (!(req.args?.type in ['uuid', 'reality-keypair', 'wg-keypair', 'vless-x25519', 'vless-mlkem768', 'ech-keypair'])) return { result: false, error: 'illegal type' }; const type = req.args?.type; @@ -61,6 +61,22 @@ const methods = { let pub = match(trim(line), /PublicKey: (.*)/); if (pub) result.public_key = pub[1]; + } else if (type in ['vless-x25519', 'vless-mlkem768']) { + let priv = match(trim(line), /PrivateKey: (.*)/); + if (priv) + result.private_key = priv[1]; + let pass = match(trim(line), /Password: (.*)/); + if (pass) + result.password = pass[1]; + let seed = match(trim(line), /Seed: (.*)/); + if (seed) + result.seed = seed[1]; + let client = match(trim(line), /Client: (.*)/); + if (client) + result.client = client[1]; + let hash = match(trim(line), /Hash32: (.*)/); + if (hash) + result.hash32 = hash[1]; } else if (type in ['ech-keypair']) { result.ech_key = result.ech_key ? result.ech_key + '\n' + trim(line) : ''; let cfg = match(trim(line), /Config: (.*)/); diff --git a/small/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js b/small/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js index e76d51fb90..76c4a195c8 100644 --- a/small/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js +++ b/small/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js @@ -212,12 +212,6 @@ return view.extend({ o.datatype = 'uinteger'; o.placeholder = _('Unmodified'); - o = s.taboption('tun', form.ListValue, 'tun_endpoint_independent_nat', _('Endpoint Independent NAT')); - o.optional = true; - o.placeholder = _('Unmodified'); - o.value('0', _('Disable')); - o.value('1', _('Enable')); - o = s.taboption('tun', form.Flag, 'tun_dns_hijack', _('Overwrite DNS Hijack')); o.rmempty = false; diff --git a/small/luci-app-nikki/po/templates/nikki.pot b/small/luci-app-nikki/po/templates/nikki.pot index 1f9b1359e2..6fc0d7ad4c 100644 --- a/small/luci-app-nikki/po/templates/nikki.pot +++ b/small/luci-app-nikki/po/templates/nikki.pot @@ -5,7 +5,7 @@ msgstr "Content-Type: text/plain; charset=UTF-8" msgid "API Listen" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:125 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:124 msgid "API Secret" msgstr "" @@ -14,16 +14,16 @@ msgstr "" msgid "Access Control" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:172 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:177 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:178 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:183 msgid "All Port" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:137 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:136 msgid "Allow Lan" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:262 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:263 msgid "Allow Mode" msgstr "" @@ -40,31 +40,31 @@ msgstr "" msgid "App Version" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:485 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:486 msgid "Append Rule" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:416 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:417 msgid "Append Rule Provider" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:472 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:473 msgid "Behavior" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:261 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:262 msgid "Block Mode" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:165 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:171 msgid "Bypass" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:167 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:173 msgid "Bypass China Mainland IP" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:180 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:186 msgid "Bypass DSCP" msgstr "" @@ -85,8 +85,8 @@ msgstr "" msgid "Clear Log" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:173 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:178 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:179 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:184 msgid "Commonly Used Port" msgstr "" @@ -114,19 +114,20 @@ msgstr "" msgid "Cron Expression" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:102 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:99 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:165 msgid "DNS" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:228 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:224 msgid "DNS Config" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:230 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:232 msgid "DNS Listen" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:241 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:242 msgid "DNS Mode" msgstr "" @@ -134,27 +135,27 @@ msgstr "" msgid "Debug Log" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:508 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:509 msgid "Destination IP" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:512 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:513 msgid "Destination IP Geo" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:509 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:510 msgid "Destination Port" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:170 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:176 msgid "Destination TCP Port to Proxy" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:175 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:181 msgid "Destination UDP Port to Proxy" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:188 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:191 msgid "Device Name" msgstr "" @@ -166,20 +167,21 @@ msgstr "" #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:73 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:79 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:85 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:132 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:140 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:206 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:216 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:238 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:267 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:273 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:279 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:285 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:291 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:360 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:366 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:372 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:559 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:131 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:139 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:188 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:208 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:229 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:239 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:268 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:274 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:280 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:286 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:292 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:361 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:367 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:373 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:560 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:38 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:45 msgid "Disable" @@ -201,64 +203,64 @@ msgstr "" msgid "Disable TCP Keep Alive" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:276 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:277 msgid "DoH Prefer HTTP/3" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:308 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:503 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:309 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:504 msgid "Domain Name" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:511 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:512 msgid "Domain Name Geo" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:506 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:507 msgid "Domain Name Keyword" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:507 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:508 msgid "Domain Name Regex" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:504 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:505 msgid "Domain Name Suffix" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:505 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:506 msgid "Domain Name Wildcard" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:168 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:165 msgid "Edit Authentications" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:222 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:218 msgid "Edit DNS Hijacks" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:254 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:255 msgid "Edit Fake-IP Filters" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:297 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:298 msgid "Edit Hosts" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:339 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:340 msgid "Edit Nameserver Policies" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:316 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:317 msgid "Edit Nameservers" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:419 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:420 msgid "Edit Rule Providers" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:488 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:489 msgid "Edit Rules" msgstr "" @@ -276,29 +278,32 @@ msgstr "" #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:74 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:80 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:86 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:133 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:141 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:176 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:207 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:217 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:239 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:268 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:274 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:280 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:286 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:292 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:305 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:324 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:347 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:357 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:361 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:367 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:373 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:399 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:427 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:496 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:560 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:568 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:132 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:140 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:173 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:185 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:189 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:209 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:226 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:230 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:240 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:269 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:275 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:281 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:287 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:293 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:306 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:325 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:348 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:358 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:362 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:368 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:374 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:400 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:428 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:497 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:561 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:569 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:33 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:66 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:77 @@ -307,10 +312,6 @@ msgstr "" msgid "Enable" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:213 -msgid "Endpoint Independent NAT" -msgstr "" - #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/profile.js:47 msgid "Expire At" msgstr "" @@ -319,11 +320,11 @@ msgstr "" msgid "External Control Config" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:264 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:265 msgid "Fake-IP Cache" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:258 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:259 msgid "Fake-IP Filter Mode" msgstr "" @@ -331,7 +332,7 @@ msgstr "" msgid "Fake-IP Ping Hijack" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:246 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:248 msgid "Fake-IP Range" msgstr "" @@ -339,15 +340,15 @@ msgstr "" msgid "Fast Reload" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:466 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:467 msgid "File Format" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:460 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:461 msgid "File Path" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:454 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:455 msgid "File Size Limit" msgstr "" @@ -355,28 +356,20 @@ msgstr "" msgid "File for Mixin" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js:50 -msgid "File for Reserved IP" -msgstr "" - -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js:51 -msgid "File for Reserved IP6" -msgstr "" - #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/app.js:103 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js:33 msgid "File:" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:378 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:379 msgid "Force Sniff Domain Name" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:203 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:205 msgid "GSO" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:209 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:211 msgid "GSO Max Size" msgstr "" @@ -388,39 +381,39 @@ msgstr "" msgid "Generate & Download" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:538 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:539 msgid "GeoData Loader" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:532 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:533 msgid "GeoIP Format" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:553 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:554 msgid "GeoIP(ASN) Url" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:550 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:551 msgid "GeoIP(DAT) Url" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:547 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:548 msgid "GeoIP(MMDB) Url" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:544 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:545 msgid "GeoSite Url" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:556 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:557 msgid "GeoX Auto Update" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:530 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:531 msgid "GeoX Config" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:562 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:563 msgid "GeoX Update Interval" msgstr "" @@ -440,7 +433,7 @@ msgstr "" msgid "Group" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:143 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:142 msgid "HTTP Port" msgstr "" @@ -464,11 +457,11 @@ msgstr "" msgid "IPv6 Proxy" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:385 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:386 msgid "Ignore Sniff Domain Name" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:135 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:134 msgid "Inbound Config" msgstr "" @@ -493,7 +486,7 @@ msgstr "" msgid "Log Level" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:199 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:201 msgid "MTU" msgstr "" @@ -501,16 +494,16 @@ msgstr "" msgid "Match Process" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:350 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:514 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:351 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:515 msgid "Matcher" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:542 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:543 msgid "Memory Conservative Loader" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:151 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:150 msgid "Mixed Port" msgstr "" @@ -518,7 +511,7 @@ msgstr "" msgid "Mixin Config" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:566 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:567 msgid "Mixin File Content" msgstr "" @@ -530,12 +523,12 @@ msgstr "" msgid "Mode" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:433 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:434 msgid "Name" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:334 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:353 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:335 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:354 msgid "Nameserver" msgstr "" @@ -544,12 +537,12 @@ msgstr "" msgid "Nikki" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:525 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:526 msgid "No Resolve" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:447 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:518 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:448 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:519 msgid "Node" msgstr "" @@ -565,55 +558,55 @@ msgstr "" msgid "Outbound Interface" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:165 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:162 msgid "Overwrite Authentication" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:219 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:215 msgid "Overwrite DNS Hijack" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:411 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:412 msgid "Overwrite Destination" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:251 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:252 msgid "Overwrite Fake-IP Filter" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:375 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:376 msgid "Overwrite Force Sniff Domain Name" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:294 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:295 msgid "Overwrite Hosts" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:382 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:383 msgid "Overwrite Ignore Sniff Domain Name" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:313 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:314 msgid "Overwrite Nameserver" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:336 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:337 msgid "Overwrite Nameserver Policy" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:389 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:390 msgid "Overwrite Sniff By Protocol" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:182 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:179 msgid "Password" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:568 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:569 msgid "Please go to the editor tab to edit the file for mixin" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:408 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:409 msgid "Port" msgstr "" @@ -621,7 +614,7 @@ msgstr "" msgid "Prefer" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:510 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:511 msgid "Process Name" msgstr "" @@ -634,12 +627,12 @@ msgstr "" msgid "Profile for Startup" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:402 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:403 msgid "Protocol" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:99 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:162 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:102 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:168 msgid "Proxy" msgstr "" @@ -661,7 +654,7 @@ msgstr "" msgid "Redirect Mode" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:155 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:154 msgid "Redirect Port" msgstr "" @@ -673,7 +666,7 @@ msgstr "" msgid "Remote" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:270 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:271 msgid "Respect Rules" msgstr "" @@ -685,7 +678,7 @@ msgstr "" msgid "Router Proxy" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:414 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:415 msgid "Rule Config" msgstr "" @@ -697,7 +690,7 @@ msgstr "" msgid "Rule Provider:" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:502 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:503 msgid "Rule Set" msgstr "" @@ -705,7 +698,7 @@ msgstr "" msgid "Running" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:147 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:146 msgid "SOCKS Port" msgstr "" @@ -713,7 +706,7 @@ msgstr "" msgid "Safe Paths" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:129 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:128 msgid "Save Proxy Selection" msgstr "" @@ -730,27 +723,27 @@ msgstr "" msgid "Skip System IPv6 Check" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:392 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:393 msgid "Sniff By Protocol" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:369 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:370 msgid "Sniff Pure IP" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:363 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:364 msgid "Sniff Redir-Host" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:355 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:356 msgid "Sniffer Config" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:192 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:194 msgid "Stack" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:541 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:542 msgid "Standard Loader" msgstr "" @@ -800,11 +793,11 @@ msgstr "" msgid "TPROXY Mode" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:160 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:158 msgid "TPROXY Port" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:186 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:183 msgid "TUN Config" msgstr "" @@ -825,9 +818,9 @@ msgstr "" msgid "Transparent Proxy with Mihomo on OpenWrt." msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:327 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:436 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:500 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:328 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:437 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:501 msgid "Type" msgstr "" @@ -866,41 +859,42 @@ msgstr "" #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:110 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:113 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:122 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:127 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:131 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:139 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:145 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:149 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:153 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:157 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:162 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:189 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:194 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:201 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:205 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:211 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:215 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:232 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:237 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:242 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:248 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:260 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:266 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:272 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:278 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:284 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:290 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:359 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:365 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:371 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:534 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:540 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:545 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:548 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:551 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:554 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:558 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:564 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:126 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:130 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:138 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:144 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:148 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:152 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:156 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:160 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:187 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:192 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:196 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:203 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:207 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:213 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:228 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:234 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:238 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:244 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:250 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:261 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:267 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:273 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:279 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:285 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:291 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:360 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:366 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:372 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:535 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:541 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:546 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:549 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:552 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:555 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:559 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:565 msgid "Unmodified" msgstr "" @@ -916,7 +910,7 @@ msgstr "" msgid "Update Dashboard" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:479 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:480 msgid "Update Interval" msgstr "" @@ -924,15 +918,15 @@ msgstr "" msgid "Upload Profile" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:442 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:443 msgid "Url" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:288 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:289 msgid "Use Hosts" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:282 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:283 msgid "Use System Hosts" msgstr "" @@ -948,6 +942,6 @@ msgstr "" msgid "User Agent" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:179 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:176 msgid "Username" msgstr "" diff --git a/small/luci-app-nikki/po/zh_Hans/nikki.po b/small/luci-app-nikki/po/zh_Hans/nikki.po index f85ac27ba3..05b1263b7d 100644 --- a/small/luci-app-nikki/po/zh_Hans/nikki.po +++ b/small/luci-app-nikki/po/zh_Hans/nikki.po @@ -12,7 +12,7 @@ msgstr "" msgid "API Listen" msgstr "API 监听" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:125 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:124 msgid "API Secret" msgstr "API 密钥" @@ -21,16 +21,16 @@ msgstr "API 密钥" msgid "Access Control" msgstr "访问控制" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:172 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:177 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:178 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:183 msgid "All Port" msgstr "全部端口" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:137 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:136 msgid "Allow Lan" msgstr "允许局域网访问" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:262 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:263 msgid "Allow Mode" msgstr "白名单模式" @@ -47,31 +47,31 @@ msgstr "插件日志" msgid "App Version" msgstr "插件版本" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:485 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:486 msgid "Append Rule" msgstr "追加规则" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:416 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:417 msgid "Append Rule Provider" msgstr "追加规则提供者" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:472 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:473 msgid "Behavior" msgstr "行为" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:261 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:262 msgid "Block Mode" msgstr "黑名单模式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:165 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:171 msgid "Bypass" msgstr "绕过" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:167 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:173 msgid "Bypass China Mainland IP" msgstr "绕过中国大陆 IP" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:180 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:186 msgid "Bypass DSCP" msgstr "绕过 DSCP" @@ -92,8 +92,8 @@ msgstr "选择配置文件" msgid "Clear Log" msgstr "清空日志" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:173 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:178 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:179 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:184 msgid "Commonly Used Port" msgstr "常用端口" @@ -121,19 +121,20 @@ msgstr "核心版本" msgid "Cron Expression" msgstr "Cron 表达式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:102 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:99 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:165 msgid "DNS" msgstr "DNS" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:228 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:224 msgid "DNS Config" msgstr "DNS 配置" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:230 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:232 msgid "DNS Listen" msgstr "DNS 监听" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:241 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:242 msgid "DNS Mode" msgstr "DNS 模式" @@ -141,27 +142,27 @@ msgstr "DNS 模式" msgid "Debug Log" msgstr "调试日志" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:508 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:509 msgid "Destination IP" msgstr "目标 IP" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:512 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:513 msgid "Destination IP Geo" msgstr "目标 IP(Geo)" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:509 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:510 msgid "Destination Port" msgstr "目标端口" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:170 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:176 msgid "Destination TCP Port to Proxy" msgstr "要代理的 TCP 目标端口" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:175 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:181 msgid "Destination UDP Port to Proxy" msgstr "要代理的 UDP 目标端口" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:188 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:191 msgid "Device Name" msgstr "设备名称" @@ -173,20 +174,21 @@ msgstr "直连模式" #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:73 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:79 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:85 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:132 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:140 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:206 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:216 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:238 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:267 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:273 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:279 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:285 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:291 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:360 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:366 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:372 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:559 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:131 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:139 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:188 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:208 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:229 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:239 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:268 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:274 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:280 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:286 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:292 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:361 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:367 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:373 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:560 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:38 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:45 msgid "Disable" @@ -208,64 +210,64 @@ msgstr "禁用回环检测" msgid "Disable TCP Keep Alive" msgstr "禁用 TCP Keep Alive" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:276 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:277 msgid "DoH Prefer HTTP/3" msgstr "DoH 优先 HTTP/3" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:308 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:503 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:309 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:504 msgid "Domain Name" msgstr "域名" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:511 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:512 msgid "Domain Name Geo" msgstr "域名(Geo)" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:506 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:507 msgid "Domain Name Keyword" msgstr "域名(关键字)" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:507 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:508 msgid "Domain Name Regex" msgstr "域名(正则表达式)" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:504 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:505 msgid "Domain Name Suffix" msgstr "域名(后缀)" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:505 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:506 msgid "Domain Name Wildcard" msgstr "域名(通配符)" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:168 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:165 msgid "Edit Authentications" msgstr "编辑身份验证" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:222 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:218 msgid "Edit DNS Hijacks" msgstr "编辑 DNS 劫持" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:254 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:255 msgid "Edit Fake-IP Filters" msgstr "编辑 Fake-IP 过滤列表" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:297 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:298 msgid "Edit Hosts" msgstr "编辑 Hosts" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:339 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:340 msgid "Edit Nameserver Policies" msgstr "编辑 DNS 服务器查询策略" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:316 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:317 msgid "Edit Nameservers" msgstr "编辑 DNS 服务器" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:419 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:420 msgid "Edit Rule Providers" msgstr "编辑规则提供者" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:488 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:489 msgid "Edit Rules" msgstr "编辑规则" @@ -283,29 +285,32 @@ msgstr "编辑器" #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:74 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:80 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:86 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:133 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:141 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:176 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:207 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:217 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:239 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:268 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:274 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:280 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:286 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:292 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:305 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:324 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:347 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:357 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:361 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:367 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:373 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:399 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:427 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:496 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:560 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:568 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:132 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:140 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:173 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:185 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:189 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:209 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:226 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:230 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:240 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:269 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:275 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:281 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:287 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:293 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:306 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:325 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:348 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:358 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:362 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:368 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:374 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:400 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:428 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:497 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:561 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:569 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:33 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:66 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:77 @@ -314,10 +319,6 @@ msgstr "编辑器" msgid "Enable" msgstr "启用" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:213 -msgid "Endpoint Independent NAT" -msgstr "独立于端点的 NAT" - #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/profile.js:47 msgid "Expire At" msgstr "到期时间" @@ -326,11 +327,11 @@ msgstr "到期时间" msgid "External Control Config" msgstr "外部控制配置" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:264 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:265 msgid "Fake-IP Cache" msgstr "Fake-IP 缓存" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:258 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:259 msgid "Fake-IP Filter Mode" msgstr "Fake-IP 过滤模式" @@ -338,7 +339,7 @@ msgstr "Fake-IP 过滤模式" msgid "Fake-IP Ping Hijack" msgstr "Fake-IP Ping 劫持" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:246 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:248 msgid "Fake-IP Range" msgstr "Fake-IP 范围" @@ -346,15 +347,15 @@ msgstr "Fake-IP 范围" msgid "Fast Reload" msgstr "快速重载" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:466 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:467 msgid "File Format" msgstr "文件格式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:460 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:461 msgid "File Path" msgstr "文件路径" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:454 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:455 msgid "File Size Limit" msgstr "文件大小限制" @@ -362,28 +363,20 @@ msgstr "文件大小限制" msgid "File for Mixin" msgstr "用于混入的文件" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js:50 -msgid "File for Reserved IP" -msgstr "IPv4 保留地址" - -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js:51 -msgid "File for Reserved IP6" -msgstr "IPv6 保留地址" - #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/app.js:103 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js:33 msgid "File:" msgstr "文件:" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:378 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:379 msgid "Force Sniff Domain Name" msgstr "强制嗅探的域名" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:203 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:205 msgid "GSO" msgstr "通用分段卸载" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:209 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:211 msgid "GSO Max Size" msgstr "分段最大长度" @@ -395,39 +388,39 @@ msgstr "全局配置" msgid "Generate & Download" msgstr "生成并下载" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:538 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:539 msgid "GeoData Loader" msgstr "GeoData 加载器" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:532 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:533 msgid "GeoIP Format" msgstr "GeoIP 格式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:553 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:554 msgid "GeoIP(ASN) Url" msgstr "GeoIP(ASN) 下载地址" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:550 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:551 msgid "GeoIP(DAT) Url" msgstr "GeoIP(DAT) 下载地址" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:547 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:548 msgid "GeoIP(MMDB) Url" msgstr "GeoIP(MMDB) 下载地址" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:544 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:545 msgid "GeoSite Url" msgstr "GeoSite 下载地址" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:556 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:557 msgid "GeoX Auto Update" msgstr "定时更新GeoX文件" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:530 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:531 msgid "GeoX Config" msgstr "GeoX 配置" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:562 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:563 msgid "GeoX Update Interval" msgstr "GeoX 文件更新间隔" @@ -447,7 +440,7 @@ msgstr "授予访问 nikki 程序的权限" msgid "Group" msgstr "用户组" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:143 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:142 msgid "HTTP Port" msgstr "HTTP 端口" @@ -471,11 +464,11 @@ msgstr "IPv6 DNS 劫持" msgid "IPv6 Proxy" msgstr "IPv6 代理" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:385 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:386 msgid "Ignore Sniff Domain Name" msgstr "忽略嗅探的域名" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:135 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:134 msgid "Inbound Config" msgstr "入站配置" @@ -500,7 +493,7 @@ msgstr "日志" msgid "Log Level" msgstr "日志级别" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:199 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:201 msgid "MTU" msgstr "最大传输单元" @@ -508,16 +501,16 @@ msgstr "最大传输单元" msgid "Match Process" msgstr "匹配进程" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:350 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:514 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:351 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:515 msgid "Matcher" msgstr "匹配" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:542 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:543 msgid "Memory Conservative Loader" msgstr "为内存受限设备优化的加载器" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:151 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:150 msgid "Mixed Port" msgstr "混合端口" @@ -525,7 +518,7 @@ msgstr "混合端口" msgid "Mixin Config" msgstr "混入配置" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:566 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:567 msgid "Mixin File Content" msgstr "混入文件内容" @@ -537,12 +530,12 @@ msgstr "混入选项" msgid "Mode" msgstr "模式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:433 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:434 msgid "Name" msgstr "名称" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:334 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:353 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:335 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:354 msgid "Nameserver" msgstr "DNS 服务器" @@ -551,12 +544,12 @@ msgstr "DNS 服务器" msgid "Nikki" msgstr "Nikki" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:525 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:526 msgid "No Resolve" msgstr "不解析" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:447 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:518 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:448 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:519 msgid "Node" msgstr "节点" @@ -572,55 +565,55 @@ msgstr "打开面板" msgid "Outbound Interface" msgstr "出站接口" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:165 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:162 msgid "Overwrite Authentication" msgstr "覆盖身份验证" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:219 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:215 msgid "Overwrite DNS Hijack" msgstr "覆盖 DNS 劫持" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:411 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:412 msgid "Overwrite Destination" msgstr "将嗅探结果作为连接目标" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:251 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:252 msgid "Overwrite Fake-IP Filter" msgstr "覆盖 Fake-IP 过滤列表" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:375 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:376 msgid "Overwrite Force Sniff Domain Name" msgstr "覆盖强制嗅探的域名" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:294 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:295 msgid "Overwrite Hosts" msgstr "覆盖 Hosts" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:382 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:383 msgid "Overwrite Ignore Sniff Domain Name" msgstr "覆盖忽略嗅探的域名" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:313 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:314 msgid "Overwrite Nameserver" msgstr "覆盖 DNS 服务器" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:336 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:337 msgid "Overwrite Nameserver Policy" msgstr "覆盖 DNS 服务器查询策略" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:389 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:390 msgid "Overwrite Sniff By Protocol" msgstr "覆盖按协议嗅探" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:182 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:179 msgid "Password" msgstr "密码" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:568 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:569 msgid "Please go to the editor tab to edit the file for mixin" msgstr "请前往编辑器标签编辑用于混入的文件" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:408 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:409 msgid "Port" msgstr "端口" @@ -628,7 +621,7 @@ msgstr "端口" msgid "Prefer" msgstr "优先" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:510 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:511 msgid "Process Name" msgstr "进程名" @@ -641,12 +634,12 @@ msgstr "配置文件" msgid "Profile for Startup" msgstr "用于启动的配置文件" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:402 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:403 msgid "Protocol" msgstr "协议" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:99 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:162 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:102 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:168 msgid "Proxy" msgstr "代理" @@ -668,7 +661,7 @@ msgstr "随机" msgid "Redirect Mode" msgstr "Redirect 模式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:155 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:154 msgid "Redirect Port" msgstr "Redirect 端口" @@ -680,7 +673,7 @@ msgstr "重载服务" msgid "Remote" msgstr "远程" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:270 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:271 msgid "Respect Rules" msgstr "遵循分流规则" @@ -692,7 +685,7 @@ msgstr "重启服务" msgid "Router Proxy" msgstr "路由器代理" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:414 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:415 msgid "Rule Config" msgstr "规则配置" @@ -704,7 +697,7 @@ msgstr "规则模式" msgid "Rule Provider:" msgstr "规则提供者:" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:502 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:503 msgid "Rule Set" msgstr "规则集" @@ -712,7 +705,7 @@ msgstr "规则集" msgid "Running" msgstr "运行中" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:147 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:146 msgid "SOCKS Port" msgstr "SOCKS 端口" @@ -720,7 +713,7 @@ msgstr "SOCKS 端口" msgid "Safe Paths" msgstr "安全路径" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:129 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:128 msgid "Save Proxy Selection" msgstr "保存节点/策略组选择" @@ -737,27 +730,27 @@ msgstr "滚动到底部" msgid "Skip System IPv6 Check" msgstr "跳过系统 IPv6 检查" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:392 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:393 msgid "Sniff By Protocol" msgstr "按协议嗅探" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:369 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:370 msgid "Sniff Pure IP" msgstr "嗅探纯 IP 连接" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:363 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:364 msgid "Sniff Redir-Host" msgstr "嗅探 Redir-Host 流量" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:355 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:356 msgid "Sniffer Config" msgstr "嗅探器配置" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:192 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:194 msgid "Stack" msgstr "栈" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:541 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:542 msgid "Standard Loader" msgstr "标准加载器" @@ -807,11 +800,11 @@ msgstr "TCP 模式" msgid "TPROXY Mode" msgstr "TPROXY 模式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:160 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:158 msgid "TPROXY Port" msgstr "TPROXY 端口" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:186 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:183 msgid "TUN Config" msgstr "TUN 配置" @@ -832,9 +825,9 @@ msgstr "总量" msgid "Transparent Proxy with Mihomo on OpenWrt." msgstr "在 OpenWrt 上使用 Mihomo 进行透明代理。" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:327 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:436 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:500 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:328 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:437 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:501 msgid "Type" msgstr "类型" @@ -873,41 +866,42 @@ msgstr "统一延迟" #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:110 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:113 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:122 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:127 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:131 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:139 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:145 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:149 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:153 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:157 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:162 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:189 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:194 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:201 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:205 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:211 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:215 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:232 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:237 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:242 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:248 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:260 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:266 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:272 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:278 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:284 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:290 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:359 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:365 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:371 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:534 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:540 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:545 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:548 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:551 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:554 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:558 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:564 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:126 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:130 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:138 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:144 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:148 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:152 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:156 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:160 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:187 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:192 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:196 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:203 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:207 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:213 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:228 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:234 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:238 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:244 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:250 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:261 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:267 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:273 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:279 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:285 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:291 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:360 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:366 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:372 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:535 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:541 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:546 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:549 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:552 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:555 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:559 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:565 msgid "Unmodified" msgstr "不修改" @@ -923,7 +917,7 @@ msgstr "更新时间" msgid "Update Dashboard" msgstr "更新面板" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:479 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:480 msgid "Update Interval" msgstr "更新间隔" @@ -931,15 +925,15 @@ msgstr "更新间隔" msgid "Upload Profile" msgstr "上传配置文件" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:442 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:443 msgid "Url" msgstr "下载地址" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:288 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:289 msgid "Use Hosts" msgstr "使用 Hosts" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:282 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:283 msgid "Use System Hosts" msgstr "使用系统的 Hosts" @@ -955,6 +949,15 @@ msgstr "用户" msgid "User Agent" msgstr "用户代理(UA)" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:179 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:176 msgid "Username" msgstr "用户名" + +#~ msgid "Endpoint Independent NAT" +#~ msgstr "独立于端点的 NAT" + +#~ msgid "File for Reserved IP" +#~ msgstr "IPv4 保留地址" + +#~ msgid "File for Reserved IP6" +#~ msgstr "IPv6 保留地址" diff --git a/small/luci-app-nikki/po/zh_Hant/nikki.po b/small/luci-app-nikki/po/zh_Hant/nikki.po index ecff6526ad..c6923645c4 100644 --- a/small/luci-app-nikki/po/zh_Hant/nikki.po +++ b/small/luci-app-nikki/po/zh_Hant/nikki.po @@ -12,7 +12,7 @@ msgstr "" msgid "API Listen" msgstr "API 監聽" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:125 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:124 msgid "API Secret" msgstr "API 密鑰" @@ -21,16 +21,16 @@ msgstr "API 密鑰" msgid "Access Control" msgstr "存取控制" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:172 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:177 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:178 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:183 msgid "All Port" msgstr "所有埠" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:137 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:136 msgid "Allow Lan" msgstr "允許區域網路存取" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:262 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:263 msgid "Allow Mode" msgstr "白名單模式" @@ -47,31 +47,31 @@ msgstr "應用程式日誌" msgid "App Version" msgstr "應用程式版本" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:485 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:486 msgid "Append Rule" msgstr "追加規則" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:416 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:417 msgid "Append Rule Provider" msgstr "追加規則提供者" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:472 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:473 msgid "Behavior" msgstr "行為" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:261 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:262 msgid "Block Mode" msgstr "黑名單模式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:165 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:171 msgid "Bypass" msgstr "繞過" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:167 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:173 msgid "Bypass China Mainland IP" msgstr "繞過中國大陸 IP" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:180 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:186 msgid "Bypass DSCP" msgstr "繞過 DSCP" @@ -92,8 +92,8 @@ msgstr "選擇設定檔" msgid "Clear Log" msgstr "清空日誌" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:173 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:178 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:179 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:184 msgid "Commonly Used Port" msgstr "常用埠" @@ -121,19 +121,20 @@ msgstr "核心版本" msgid "Cron Expression" msgstr "Cron 表達式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:102 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:99 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:165 msgid "DNS" msgstr "DNS" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:228 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:224 msgid "DNS Config" msgstr "DNS 設定" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:230 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:232 msgid "DNS Listen" msgstr "DNS 監聽" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:241 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:242 msgid "DNS Mode" msgstr "DNS 模式" @@ -141,27 +142,27 @@ msgstr "DNS 模式" msgid "Debug Log" msgstr "除錯日誌" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:508 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:509 msgid "Destination IP" msgstr "目標 IP" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:512 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:513 msgid "Destination IP Geo" msgstr "目標 IP(地理位置)" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:509 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:510 msgid "Destination Port" msgstr "目標埠" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:170 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:176 msgid "Destination TCP Port to Proxy" msgstr "要代理的 TCP 目標埠" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:175 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:181 msgid "Destination UDP Port to Proxy" msgstr "要代理的 UDP 目標埠" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:188 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:191 msgid "Device Name" msgstr "裝置名稱" @@ -173,20 +174,21 @@ msgstr "直連模式" #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:73 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:79 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:85 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:132 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:140 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:206 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:216 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:238 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:267 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:273 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:279 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:285 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:291 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:360 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:366 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:372 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:559 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:131 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:139 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:188 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:208 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:229 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:239 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:268 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:274 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:280 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:286 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:292 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:361 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:367 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:373 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:560 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:38 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:45 msgid "Disable" @@ -208,64 +210,64 @@ msgstr "停用迴路檢測" msgid "Disable TCP Keep Alive" msgstr "停用 TCP Keep Alive" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:276 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:277 msgid "DoH Prefer HTTP/3" msgstr "DoH 優先使用 HTTP/3" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:308 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:503 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:309 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:504 msgid "Domain Name" msgstr "網域名稱" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:511 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:512 msgid "Domain Name Geo" msgstr "網域名稱(地理位置)" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:506 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:507 msgid "Domain Name Keyword" msgstr "網域名稱(關鍵字)" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:507 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:508 msgid "Domain Name Regex" msgstr "網域名稱(正則表達式)" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:504 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:505 msgid "Domain Name Suffix" msgstr "網域名稱(後綴)" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:505 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:506 msgid "Domain Name Wildcard" msgstr "網域名稱(通配符)" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:168 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:165 msgid "Edit Authentications" msgstr "編輯身分驗證" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:222 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:218 msgid "Edit DNS Hijacks" msgstr "編輯 DNS 劫持" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:254 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:255 msgid "Edit Fake-IP Filters" msgstr "編輯 Fake-IP 過濾清單" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:297 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:298 msgid "Edit Hosts" msgstr "編輯 Hosts" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:339 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:340 msgid "Edit Nameserver Policies" msgstr "編輯 DNS 伺服器查詢策略" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:316 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:317 msgid "Edit Nameservers" msgstr "編輯 DNS 伺服器" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:419 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:420 msgid "Edit Rule Providers" msgstr "編輯規則提供者" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:488 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:489 msgid "Edit Rules" msgstr "編輯規則" @@ -283,29 +285,32 @@ msgstr "編輯器" #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:74 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:80 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:86 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:133 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:141 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:176 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:207 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:217 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:239 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:268 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:274 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:280 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:286 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:292 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:305 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:324 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:347 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:357 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:361 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:367 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:373 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:399 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:427 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:496 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:560 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:568 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:132 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:140 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:173 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:185 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:189 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:209 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:226 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:230 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:240 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:269 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:275 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:281 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:287 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:293 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:306 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:325 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:348 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:358 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:362 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:368 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:374 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:400 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:428 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:497 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:561 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:569 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:33 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:66 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:77 @@ -314,10 +319,6 @@ msgstr "編輯器" msgid "Enable" msgstr "啟用" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:213 -msgid "Endpoint Independent NAT" -msgstr "端點獨立 NAT" - #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/profile.js:47 msgid "Expire At" msgstr "到期時間" @@ -326,11 +327,11 @@ msgstr "到期時間" msgid "External Control Config" msgstr "外部控制設定" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:264 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:265 msgid "Fake-IP Cache" msgstr "Fake-IP 快取" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:258 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:259 msgid "Fake-IP Filter Mode" msgstr "Fake-IP 過濾模式" @@ -338,7 +339,7 @@ msgstr "Fake-IP 過濾模式" msgid "Fake-IP Ping Hijack" msgstr "Fake-IP Ping 劫持" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:246 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:248 msgid "Fake-IP Range" msgstr "Fake-IP 範圍" @@ -346,15 +347,15 @@ msgstr "Fake-IP 範圍" msgid "Fast Reload" msgstr "快速重載" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:466 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:467 msgid "File Format" msgstr "檔案格式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:460 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:461 msgid "File Path" msgstr "檔案路徑" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:454 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:455 msgid "File Size Limit" msgstr "檔案大小限制" @@ -362,28 +363,20 @@ msgstr "檔案大小限制" msgid "File for Mixin" msgstr "用於混入的檔案" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js:50 -msgid "File for Reserved IP" -msgstr "IPv4 保留地址" - -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js:51 -msgid "File for Reserved IP6" -msgstr "IPv6 保留地址" - #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/app.js:103 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js:33 msgid "File:" msgstr "檔案:" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:378 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:379 msgid "Force Sniff Domain Name" msgstr "強制嗅探的網域名稱" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:203 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:205 msgid "GSO" msgstr "通用分段卸載" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:209 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:211 msgid "GSO Max Size" msgstr "分段最大長度" @@ -395,39 +388,39 @@ msgstr "一般設定" msgid "Generate & Download" msgstr "生成並下載" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:538 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:539 msgid "GeoData Loader" msgstr "GeoData 載入器" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:532 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:533 msgid "GeoIP Format" msgstr "GeoIP 格式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:553 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:554 msgid "GeoIP(ASN) Url" msgstr "GeoIP(ASN) 下載網址" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:550 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:551 msgid "GeoIP(DAT) Url" msgstr "GeoIP(DAT) 下載網址" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:547 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:548 msgid "GeoIP(MMDB) Url" msgstr "GeoIP(MMDB) 下載網址" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:544 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:545 msgid "GeoSite Url" msgstr "GeoSite 下載網址" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:556 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:557 msgid "GeoX Auto Update" msgstr "定時更新 GeoX 檔案" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:530 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:531 msgid "GeoX Config" msgstr "GeoX 設定" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:562 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:563 msgid "GeoX Update Interval" msgstr "GeoX 檔案更新間隔" @@ -447,7 +440,7 @@ msgstr "授予存取 nikki 程序的權限" msgid "Group" msgstr "使用者群組" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:143 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:142 msgid "HTTP Port" msgstr "HTTP 埠" @@ -471,11 +464,11 @@ msgstr "IPv6 DNS 劫持" msgid "IPv6 Proxy" msgstr "IPv6 代理" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:385 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:386 msgid "Ignore Sniff Domain Name" msgstr "忽略嗅探的網域名稱" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:135 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:134 msgid "Inbound Config" msgstr "入站設定" @@ -500,7 +493,7 @@ msgstr "日誌" msgid "Log Level" msgstr "日誌等級" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:199 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:201 msgid "MTU" msgstr "最大傳輸單元" @@ -508,16 +501,16 @@ msgstr "最大傳輸單元" msgid "Match Process" msgstr "匹配程序" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:350 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:514 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:351 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:515 msgid "Matcher" msgstr "匹配器" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:542 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:543 msgid "Memory Conservative Loader" msgstr "為記憶體受限裝置最佳化的載入器" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:151 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:150 msgid "Mixed Port" msgstr "混合埠" @@ -525,7 +518,7 @@ msgstr "混合埠" msgid "Mixin Config" msgstr "混入設定" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:566 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:567 msgid "Mixin File Content" msgstr "混入檔案內容" @@ -537,12 +530,12 @@ msgstr "混入選項" msgid "Mode" msgstr "模式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:433 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:434 msgid "Name" msgstr "名稱" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:334 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:353 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:335 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:354 msgid "Nameserver" msgstr "DNS 伺服器" @@ -551,12 +544,12 @@ msgstr "DNS 伺服器" msgid "Nikki" msgstr "Nikki" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:525 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:526 msgid "No Resolve" msgstr "不解析" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:447 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:518 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:448 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:519 msgid "Node" msgstr "節點" @@ -572,55 +565,55 @@ msgstr "開啟儀表板" msgid "Outbound Interface" msgstr "出站介面" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:165 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:162 msgid "Overwrite Authentication" msgstr "覆寫身分驗證" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:219 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:215 msgid "Overwrite DNS Hijack" msgstr "覆寫 DNS 劫持" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:411 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:412 msgid "Overwrite Destination" msgstr "將嗅探結果作為連線目標" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:251 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:252 msgid "Overwrite Fake-IP Filter" msgstr "覆寫 Fake-IP 過濾清單" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:375 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:376 msgid "Overwrite Force Sniff Domain Name" msgstr "覆寫強制嗅探的網域名稱" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:294 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:295 msgid "Overwrite Hosts" msgstr "覆寫 Hosts" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:382 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:383 msgid "Overwrite Ignore Sniff Domain Name" msgstr "覆寫忽略嗅探的網域名稱" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:313 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:314 msgid "Overwrite Nameserver" msgstr "覆寫 DNS 伺服器" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:336 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:337 msgid "Overwrite Nameserver Policy" msgstr "覆寫 DNS 伺服器查詢策略" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:389 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:390 msgid "Overwrite Sniff By Protocol" msgstr "覆寫按協定嗅探" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:182 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:179 msgid "Password" msgstr "密碼" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:568 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:569 msgid "Please go to the editor tab to edit the file for mixin" msgstr "請前往編輯器標籤編輯用於混入的檔案" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:408 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:409 msgid "Port" msgstr "埠" @@ -628,7 +621,7 @@ msgstr "埠" msgid "Prefer" msgstr "優先" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:510 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:511 msgid "Process Name" msgstr "程序名稱" @@ -641,12 +634,12 @@ msgstr "設定檔" msgid "Profile for Startup" msgstr "用於啟動的設定檔" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:402 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:403 msgid "Protocol" msgstr "協定" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:99 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:162 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:102 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:168 msgid "Proxy" msgstr "代理" @@ -668,7 +661,7 @@ msgstr "隨機" msgid "Redirect Mode" msgstr "Redirect 模式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:155 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:154 msgid "Redirect Port" msgstr "Redirect 埠" @@ -680,7 +673,7 @@ msgstr "重新載入服務" msgid "Remote" msgstr "遠端" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:270 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:271 msgid "Respect Rules" msgstr "遵循分流規則" @@ -692,7 +685,7 @@ msgstr "重新啟動服務" msgid "Router Proxy" msgstr "路由器代理" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:414 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:415 msgid "Rule Config" msgstr "規則設定" @@ -704,7 +697,7 @@ msgstr "規則模式" msgid "Rule Provider:" msgstr "規則提供者:" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:502 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:503 msgid "Rule Set" msgstr "規則集" @@ -712,7 +705,7 @@ msgstr "規則集" msgid "Running" msgstr "執行中" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:147 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:146 msgid "SOCKS Port" msgstr "SOCKS 埠" @@ -720,7 +713,7 @@ msgstr "SOCKS 埠" msgid "Safe Paths" msgstr "安全路徑" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:129 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:128 msgid "Save Proxy Selection" msgstr "儲存節點/策略群組選擇" @@ -737,27 +730,27 @@ msgstr "捲動到底部" msgid "Skip System IPv6 Check" msgstr "跳過系統 IPv6 檢查" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:392 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:393 msgid "Sniff By Protocol" msgstr "按協定嗅探" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:369 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:370 msgid "Sniff Pure IP" msgstr "嗅探純 IP 連線" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:363 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:364 msgid "Sniff Redir-Host" msgstr "嗅探 Redir-Host 流量" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:355 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:356 msgid "Sniffer Config" msgstr "嗅探器設定" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:192 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:194 msgid "Stack" msgstr "堆疊" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:541 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:542 msgid "Standard Loader" msgstr "標準載入器" @@ -807,11 +800,11 @@ msgstr "TCP 模式" msgid "TPROXY Mode" msgstr "TPROXY 模式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:160 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:158 msgid "TPROXY Port" msgstr "TPROXY 埠" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:186 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:183 msgid "TUN Config" msgstr "TUN 設定" @@ -832,9 +825,9 @@ msgstr "總計" msgid "Transparent Proxy with Mihomo on OpenWrt." msgstr "在 OpenWrt 上使用 Mihomo 進行透明代理." -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:327 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:436 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:500 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:328 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:437 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:501 msgid "Type" msgstr "類型" @@ -873,41 +866,42 @@ msgstr "統一延遲" #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:110 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:113 #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:122 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:127 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:131 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:139 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:145 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:149 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:153 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:157 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:162 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:189 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:194 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:201 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:205 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:211 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:215 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:232 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:237 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:242 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:248 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:260 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:266 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:272 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:278 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:284 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:290 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:359 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:365 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:371 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:534 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:540 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:545 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:548 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:551 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:554 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:558 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:564 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:126 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:130 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:138 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:144 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:148 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:152 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:156 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:160 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:187 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:192 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:196 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:203 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:207 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:213 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:228 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:234 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:238 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:244 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:250 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:261 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:267 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:273 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:279 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:285 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:291 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:360 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:366 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:372 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:535 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:541 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:546 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:549 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:552 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:555 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:559 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:565 msgid "Unmodified" msgstr "不修改" @@ -923,7 +917,7 @@ msgstr "更新時間" msgid "Update Dashboard" msgstr "更新儀表板" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:479 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:480 msgid "Update Interval" msgstr "更新間隔" @@ -931,15 +925,15 @@ msgstr "更新間隔" msgid "Upload Profile" msgstr "上傳設定檔" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:442 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:443 msgid "Url" msgstr "下載網址" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:288 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:289 msgid "Use Hosts" msgstr "使用 Hosts" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:282 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:283 msgid "Use System Hosts" msgstr "使用系統的 Hosts" @@ -955,6 +949,15 @@ msgstr "使用者" msgid "User Agent" msgstr "使用者代理(UA)" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:179 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js:176 msgid "Username" msgstr "使用者名稱" + +#~ msgid "Endpoint Independent NAT" +#~ msgstr "端點獨立 NAT" + +#~ msgid "File for Reserved IP" +#~ msgstr "IPv4 保留地址" + +#~ msgid "File for Reserved IP6" +#~ msgstr "IPv6 保留地址" diff --git a/small/luci-app-passwall/root/usr/share/passwall/rule_update.lua b/small/luci-app-passwall/root/usr/share/passwall/rule_update.lua index 8764fe21bb..f447280208 100755 --- a/small/luci-app-passwall/root/usr/share/passwall/rule_update.lua +++ b/small/luci-app-passwall/root/usr/share/passwall/rule_update.lua @@ -95,7 +95,7 @@ end -- curl local function curl(url, file, valifile) local args = { - "-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3" + "-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3", "--max-time 300", "--speed-limit 51200 --speed-time 15" } if file then args[#args + 1] = "-o " .. file @@ -126,7 +126,8 @@ end local function non_file_check(file_path, vali_file) if fs.readfile(file_path, 10) then - local remote_file_size = tonumber(sys.exec("cat " .. vali_file .. " | grep -i 'Content-Length' | awk '{print $2}'")) + local size_str = sys.exec("grep -i 'Content-Length' " .. vali_file .. " | tail -n1 | sed 's/[^0-9]//g'") + local remote_file_size = tonumber(size_str ~= "" and size_str or nil) local local_file_size = tonumber(fs.stat(file_path, "size")) if remote_file_size and local_file_size then if remote_file_size == local_file_size then @@ -317,6 +318,7 @@ local function fetch_geofile(geo_name, geo_type, url) local down_filename = url:match("^.*/([^/?#]+)") local sha_url = url:gsub(down_filename, down_filename .. ".sha256sum") local sha_path = tmp_path .. ".sha256sum" + local vali_file = tmp_path .. ".vali" local function verify_sha256(sha_file) return sys.call("sha256sum -c " .. sha_file .. " > /dev/null 2>&1") == 0 @@ -346,7 +348,18 @@ local function fetch_geofile(geo_name, geo_type, url) end end - if curl(url, tmp_path) == 200 then + local sret_tmp = curl(url, tmp_path, vali_file) + if sret_tmp == 200 and non_file_check(tmp_path, vali_file) then + log(geo_type .. " 下载文件过程出错,尝试重新下载。") + os.remove(tmp_path) + os.remove(vali_file) + sret_tmp = curl(url, tmp_path, vali_file) + if sret_tmp == 200 and non_file_check(tmp_path, vali_file) then + sret_tmp = 0 + log(geo_type .. " 下载文件过程出错,请检查网络或下载链接后重试!") + end + end + if sret_tmp == 200 then if sha_verify then if verify_sha256(sha_path) then sys.call(string.format("mkdir -p %s && cp -f %s %s", asset_location, tmp_path, asset_path)) @@ -451,6 +464,7 @@ end local function remove_tmp_geofile(name) os.remove("/tmp/" .. name .. ".dat") os.remove("/tmp/" .. name .. ".dat.sha256sum") + os.remove("/tmp/" .. name .. ".dat.vali") end if geo2rule == "1" then diff --git a/small/mihomo/Makefile b/small/mihomo/Makefile index 6d072ef3dc..c454251be2 100644 --- a/small/mihomo/Makefile +++ b/small/mihomo/Makefile @@ -5,12 +5,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=mihomo -PKG_VERSION:=1.19.12 +PKG_VERSION:=1.19.13 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/metacubex/mihomo/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=9f2d029f7d074cb2f0f9c7bc59f47fddf48bd9ce2ce3532cd91d00fd89ee25f7 +PKG_HASH:=dcbdcfb84bde1d70758247e7f3a4fbd4e4771655d01b23aabbdf3cafcaf749b1 PKG_MAINTAINER:=Anya Lin PKG_LICENSE:=GPL-2.0 diff --git a/small/nikki/files/ucode/hijack.ut b/small/nikki/files/ucode/hijack.ut index 4dcd4fb9c9..4bc9f87927 100644 --- a/small/nikki/files/ucode/hijack.ut +++ b/small/nikki/files/ucode/hijack.ut @@ -21,11 +21,22 @@ const redir_port = profile['redir-port']; const tproxy_port = profile['tproxy-port']; - const dns_listen = profile['dns']['listen']; - const dns_port = substr(dns_listen, rindex(dns_listen, ':') + 1); - const fake_ip_range = profile['dns']['fake-ip-range']; + let dns_listen; + let dns_port; + let fake_ip_range; + if (profile['dns']) { + dns_listen = profile['dns']['listen']; + const dns_listen_rindex = rindex(dns_listen, ':'); + if (dns_listen_rindex >= 0 && dns_listen_rindex + 1 < length(dns_listen)) { + dns_port = substr(dns_listen, dns_listen_rindex + 1); + } + fake_ip_range = profile['dns']['fake-ip-range']; + } - const tun_device = profile['tun']['device']; + let tun_device; + if (profile['tun']) { + tun_device = profile['tun']['device']; + } uci.load('nikki'); diff --git a/small/nikki/files/ucode/mixin.uc b/small/nikki/files/ucode/mixin.uc index 9d17cf57f7..28e2535f6c 100644 --- a/small/nikki/files/ucode/mixin.uc +++ b/small/nikki/files/ucode/mixin.uc @@ -57,7 +57,6 @@ config['tun']['stack'] = uci.get('nikki', 'mixin', 'tun_stack'); config['tun']['mtu'] = uci_int(uci.get('nikki', 'mixin', 'tun_mtu')); config['tun']['gso'] = uci_bool(uci.get('nikki', 'mixin', 'tun_gso')); config['tun']['gso-max-size'] = uci_int(uci.get('nikki', 'mixin', 'tun_gso_max_size')); -config['tun']['endpoint-independent-nat'] = uci_bool(uci.get('nikki', 'mixin', 'tun_endpoint_independent_nat')); if (uci_bool(uci.get('nikki', 'mixin', 'tun_dns_hijack'))) { config['tun']['dns-hijack'] = uci_array(uci.get('nikki', 'mixin', 'tun_dns_hijacks')); } diff --git a/small/sing-box/Makefile b/small/sing-box/Makefile index 3fef967557..36888f11eb 100644 --- a/small/sing-box/Makefile +++ b/small/sing-box/Makefile @@ -5,12 +5,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=sing-box -PKG_VERSION:=1.12.3 +PKG_VERSION:=1.12.4 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/SagerNet/sing-box/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=3dce8ee383655908451f7f193714f0c8f90b8fd4baecb8e7e3948d263d766359 +PKG_HASH:=9a14ffa04fee1a1091ca1995a45f3e3feee460bddff0a72da2febc05a05b2660 PKG_LICENSE:=GPL-3.0-or-later PKG_LICENSE_FILES:=LICENSE diff --git a/small/v2raya/Makefile b/small/v2raya/Makefile index 2688cd7253..59d144385c 100644 --- a/small/v2raya/Makefile +++ b/small/v2raya/Makefile @@ -5,12 +5,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=v2rayA -PKG_VERSION:=2.2.7 +PKG_VERSION:=2.2.7.1 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/v2rayA/v2rayA/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=d37cb7e0ef045976ef50e298b11a78cdcdfe07d13c506b3b2f2ee40dd87bfbad +PKG_HASH:=8996ce3ac42f4998a433ab4f8968c7da656baae40b34c154705ecba4274f012d PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)/service PKG_LICENSE:=AGPL-3.0-only @@ -60,7 +60,7 @@ define Download/v2raya-web URL:=https://github.com/v2rayA/v2rayA/releases/download/v$(PKG_VERSION)/ URL_FILE:=web.tar.gz FILE:=$(WEB_FILE) - HASH:=cbb046f627616ba5e45c04ee9d18ab0f28c3f1340bc272cff1215c76fd025c8b + HASH:=26eaea7b367b36b844c98c0b537fb05482595329ac5fe0ea2293f77bc9d1aac9 endef define Build/Prepare diff --git a/xray-core/README.md b/xray-core/README.md index 511efe20ff..c600de24f9 100644 --- a/xray-core/README.md +++ b/xray-core/README.md @@ -11,8 +11,10 @@ [Project X NFT](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1) - **ETH/USDT/USDC: `0xDc3Fe44F0f25D13CACb1C4896CD0D321df3146Ee`** +- **Project X NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1** +- **VLESS NFT: https://opensea.io/collection/vless** - **REALITY NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2** -- **Related links: https://opensea.io/collection/xtls, [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633), [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113)** +- **Related links: [VLESS Post-Quantum Encryption](https://github.com/XTLS/Xray-core/pull/5067), [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113), [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633)** ## License diff --git a/xray-core/common/protocol/headers.go b/xray-core/common/protocol/headers.go index 261e21d934..fb785d7378 100644 --- a/xray-core/common/protocol/headers.go +++ b/xray-core/common/protocol/headers.go @@ -79,20 +79,18 @@ type CommandSwitchAccount struct { } var ( - hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ + // Keep in sync with crypto/tls/cipher_suites.go. + hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3 hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL - // Keep in sync with crypto/aes/cipher_s390x.go. - hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && - (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM) + hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH + hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" - hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 || - runtime.GOARCH == "arm64" && hasGCMAsmARM64 || - runtime.GOARCH == "s390x" && hasGCMAsmS390X + HasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64 ) func (sc *SecurityConfig) GetSecurityType() SecurityType { if sc == nil || sc.Type == SecurityType_AUTO { - if hasAESGCMHardwareSupport { + if HasAESGCMHardwareSupport { return SecurityType_AES128_GCM } return SecurityType_CHACHA20_POLY1305 diff --git a/xray-core/go.mod b/xray-core/go.mod index c5e8d2e2a8..13a0bf8c77 100644 --- a/xray-core/go.mod +++ b/xray-core/go.mod @@ -16,10 +16,10 @@ require ( github.com/sagernet/sing v0.5.1 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 - github.com/stretchr/testify v1.11.0 + github.com/stretchr/testify v1.11.1 github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e github.com/vishvananda/netlink v1.3.1 - github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7 + github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/crypto v0.41.0 golang.org/x/net v0.43.0 diff --git a/xray-core/go.sum b/xray-core/go.sum index f6c95e350e..268254b783 100644 --- a/xray-core/go.sum +++ b/xray-core/go.sum @@ -67,16 +67,16 @@ github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1 github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= -github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7 h1:Ript0vN+nSO33+Vj4n0mgNY5M+oOxFQJdrJ1VnwTBO0= -github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0= +github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f h1:o1Kryl9qEYYzNep9RId9DM1kBn8tBrcK5UJnti/l0NI= +github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= diff --git a/xray-core/infra/conf/vless.go b/xray-core/infra/conf/vless.go index 1a7341b30d..5d10cc97a3 100644 --- a/xray-core/infra/conf/vless.go +++ b/xray-core/infra/conf/vless.go @@ -1,6 +1,7 @@ package conf import ( + "encoding/base64" "encoding/json" "path/filepath" "runtime" @@ -80,10 +81,45 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) { config.Clients[idx] = user } - if c.Decryption != "none" { - return nil, errors.New(`VLESS settings: please add/set "decryption":"none" to every settings`) - } config.Decryption = c.Decryption + if !func() bool { + s := strings.Split(config.Decryption, ".") + if len(s) < 4 || s[0] != "mlkem768x25519plus" { + return false + } + switch s[1] { + case "native": + case "xorpub": + config.XorMode = 1 + case "random": + config.XorMode = 2 + default: + return false + } + if s[2] != "1rtt" { + t := strings.TrimSuffix(s[2], "s") + if t == s[2] { + return false + } + i, err := strconv.Atoi(t) + if err != nil { + return false + } + config.Seconds = uint32(i) + } + for i := 3; i < len(s); i++ { + if b, _ := base64.RawURLEncoding.DecodeString(s[i]); len(b) != 32 && len(b) != 64 { + return false + } + } + config.Decryption = config.Decryption[27+len(s[2]):] + return true + }() && config.Decryption != "none" { + if config.Decryption == "" { + return nil, errors.New(`VLESS settings: please add/set "decryption":"none" to every settings`) + } + return nil, errors.New(`VLESS settings: unsupported "decryption": ` + config.Decryption) + } for _, fb := range c.Fallbacks { var i uint16 @@ -155,16 +191,16 @@ type VLessOutboundConfig struct { func (c *VLessOutboundConfig) Build() (proto.Message, error) { config := new(outbound.Config) - if len(c.Vnext) == 0 { - return nil, errors.New(`VLESS settings: "vnext" is empty`) + if len(c.Vnext) != 1 { + return nil, errors.New(`VLESS settings: "vnext" should have one and only one member`) } config.Vnext = make([]*protocol.ServerEndpoint, len(c.Vnext)) for idx, rec := range c.Vnext { if rec.Address == nil { return nil, errors.New(`VLESS vnext: "address" is not set`) } - if len(rec.Users) == 0 { - return nil, errors.New(`VLESS vnext: "users" is empty`) + if len(rec.Users) != 1 { + return nil, errors.New(`VLESS vnext: "users" should have one and only one member`) } spec := &protocol.ServerEndpoint{ Address: rec.Address.Build(), @@ -193,8 +229,39 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) { return nil, errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`) } - if account.Encryption != "none" { - return nil, errors.New(`VLESS users: please add/set "encryption":"none" for every user`) + if !func() bool { + s := strings.Split(account.Encryption, ".") + if len(s) < 4 || s[0] != "mlkem768x25519plus" { + return false + } + switch s[1] { + case "native": + case "xorpub": + account.XorMode = 1 + case "random": + account.XorMode = 2 + default: + return false + } + switch s[2] { + case "1rtt": + case "0rtt": + account.Seconds = 1 + default: + return false + } + for i := 3; i < len(s); i++ { + if b, _ := base64.RawURLEncoding.DecodeString(s[i]); len(b) != 32 && len(b) != 1184 { + return false + } + } + account.Encryption = account.Encryption[27+len(s[2]):] + return true + }() && account.Encryption != "none" { + if account.Encryption == "" { + return nil, errors.New(`VLESS users: please add/set "encryption":"none" for every user`) + } + return nil, errors.New(`VLESS users: unsupported "encryption": ` + account.Encryption) } user.Account = serial.ToTypedMessage(account) diff --git a/xray-core/main/commands/all/commands.go b/xray-core/main/commands/all/commands.go index 3667a1d8d3..9f8270f944 100644 --- a/xray-core/main/commands/all/commands.go +++ b/xray-core/main/commands/all/commands.go @@ -17,5 +17,6 @@ func init() { cmdX25519, cmdWG, cmdMLDSA65, + cmdMLKEM768, ) } diff --git a/xray-core/main/commands/all/curve25519.go b/xray-core/main/commands/all/curve25519.go index bb706c6c2a..16ca8c7cf6 100644 --- a/xray-core/main/commands/all/curve25519.go +++ b/xray-core/main/commands/all/curve25519.go @@ -1,17 +1,15 @@ package all import ( + "crypto/ecdh" "crypto/rand" "encoding/base64" "fmt" - "golang.org/x/crypto/curve25519" + "lukechampine.com/blake3" ) func Curve25519Genkey(StdEncoding bool, input_base64 string) { - var output string - var err error - var privateKey, publicKey []byte var encoding *base64.Encoding if *input_stdEncoding || StdEncoding { encoding = base64.StdEncoding @@ -19,40 +17,35 @@ func Curve25519Genkey(StdEncoding bool, input_base64 string) { encoding = base64.RawURLEncoding } + var privateKey []byte if len(input_base64) > 0 { - privateKey, err = encoding.DecodeString(input_base64) - if err != nil { - output = err.Error() - goto out - } - if len(privateKey) != curve25519.ScalarSize { - output = "Invalid length of private key." - goto out + privateKey, _ = encoding.DecodeString(input_base64) + if len(privateKey) != 32 { + fmt.Println("Invalid length of X25519 private key.") + return } } - if privateKey == nil { - privateKey = make([]byte, curve25519.ScalarSize) - if _, err = rand.Read(privateKey); err != nil { - output = err.Error() - goto out - } + privateKey = make([]byte, 32) + rand.Read(privateKey) } // Modify random bytes using algorithm described at: - // https://cr.yp.to/ecdh.html. + // https://cr.yp.to/ecdh.html + // (Just to make sure printing the real private key) privateKey[0] &= 248 privateKey[31] &= 127 privateKey[31] |= 64 - if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil { - output = err.Error() - goto out + key, err := ecdh.X25519().NewPrivateKey(privateKey) + if err != nil { + fmt.Println(err.Error()) + return } - - output = fmt.Sprintf("Private key: %v\nPublic key: %v", + password := key.PublicKey().Bytes() + hash32 := blake3.Sum256(password) + fmt.Printf("PrivateKey: %v\nPassword: %v\nHash32: %v", encoding.EncodeToString(privateKey), - encoding.EncodeToString(publicKey)) -out: - fmt.Println(output) + encoding.EncodeToString(password), + encoding.EncodeToString(hash32[:])) } diff --git a/xray-core/main/commands/all/mldsa65.go b/xray-core/main/commands/all/mldsa65.go index fe0f5eb425..495fb088e9 100644 --- a/xray-core/main/commands/all/mldsa65.go +++ b/xray-core/main/commands/all/mldsa65.go @@ -11,9 +11,9 @@ import ( var cmdMLDSA65 = &base.Command{ UsageLine: `{{.Exec}} mldsa65 [-i "seed (base64.RawURLEncoding)"]`, - Short: `Generate key pair for ML-DSA-65 post-quantum signature`, + Short: `Generate key pair for ML-DSA-65 post-quantum signature (REALITY)`, Long: ` -Generate key pair for ML-DSA-65 post-quantum signature. +Generate key pair for ML-DSA-65 post-quantum signature (REALITY). Random: {{.Exec}} mldsa65 @@ -25,12 +25,16 @@ func init() { cmdMLDSA65.Run = executeMLDSA65 // break init loop } -var input_seed = cmdMLDSA65.Flag.String("i", "", "") +var input_mldsa65 = cmdMLDSA65.Flag.String("i", "", "") func executeMLDSA65(cmd *base.Command, args []string) { var seed [32]byte - if len(*input_seed) > 0 { - s, _ := base64.RawURLEncoding.DecodeString(*input_seed) + if len(*input_mldsa65) > 0 { + s, _ := base64.RawURLEncoding.DecodeString(*input_mldsa65) + if len(s) != 32 { + fmt.Println("Invalid length of ML-DSA-65 seed.") + return + } seed = [32]byte(s) } else { rand.Read(seed[:]) diff --git a/xray-core/main/commands/all/mlkem768.go b/xray-core/main/commands/all/mlkem768.go new file mode 100644 index 0000000000..0f6e707b53 --- /dev/null +++ b/xray-core/main/commands/all/mlkem768.go @@ -0,0 +1,50 @@ +package all + +import ( + "crypto/mlkem" + "crypto/rand" + "encoding/base64" + "fmt" + + "github.com/xtls/xray-core/main/commands/base" + "lukechampine.com/blake3" +) + +var cmdMLKEM768 = &base.Command{ + UsageLine: `{{.Exec}} mlkem768 [-i "seed (base64.RawURLEncoding)"]`, + Short: `Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS)`, + Long: ` +Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS). + +Random: {{.Exec}} mlkem768 + +From seed: {{.Exec}} mlkem768 -i "seed (base64.RawURLEncoding)" +`, +} + +func init() { + cmdMLKEM768.Run = executeMLKEM768 // break init loop +} + +var input_mlkem768 = cmdMLKEM768.Flag.String("i", "", "") + +func executeMLKEM768(cmd *base.Command, args []string) { + var seed [64]byte + if len(*input_mlkem768) > 0 { + s, _ := base64.RawURLEncoding.DecodeString(*input_mlkem768) + if len(s) != 64 { + fmt.Println("Invalid length of ML-KEM-768 seed.") + return + } + seed = [64]byte(s) + } else { + rand.Read(seed[:]) + } + key, _ := mlkem.NewDecapsulationKey768(seed[:]) + client := key.EncapsulationKey().Bytes() + hash32 := blake3.Sum256(client) + fmt.Printf("Seed: %v\nClient: %v\nHash32: %v", + base64.RawURLEncoding.EncodeToString(seed[:]), + base64.RawURLEncoding.EncodeToString(client), + base64.RawURLEncoding.EncodeToString(hash32[:])) +} diff --git a/xray-core/main/commands/all/uuid.go b/xray-core/main/commands/all/uuid.go index b01e88f058..1fe27bf54a 100644 --- a/xray-core/main/commands/all/uuid.go +++ b/xray-core/main/commands/all/uuid.go @@ -9,9 +9,9 @@ import ( var cmdUUID = &base.Command{ UsageLine: `{{.Exec}} uuid [-i "example"]`, - Short: `Generate UUIDv4 or UUIDv5`, + Short: `Generate UUIDv4 or UUIDv5 (VLESS)`, Long: ` -Generate UUIDv4 or UUIDv5. +Generate UUIDv4 or UUIDv5 (VLESS). UUIDv4 (random): {{.Exec}} uuid diff --git a/xray-core/main/commands/all/wg.go b/xray-core/main/commands/all/wg.go index 70da46682b..1de0e515ee 100644 --- a/xray-core/main/commands/all/wg.go +++ b/xray-core/main/commands/all/wg.go @@ -6,9 +6,9 @@ import ( var cmdWG = &base.Command{ UsageLine: `{{.Exec}} wg [-i "private key (base64.StdEncoding)"]`, - Short: `Generate key pair for wireguard key exchange`, + Short: `Generate key pair for X25519 key exchange (WireGuard)`, Long: ` -Generate key pair for wireguard key exchange. +Generate key pair for X25519 key exchange (WireGuard). Random: {{.Exec}} wg diff --git a/xray-core/main/commands/all/x25519.go b/xray-core/main/commands/all/x25519.go index 73f669b269..7ef23f032b 100644 --- a/xray-core/main/commands/all/x25519.go +++ b/xray-core/main/commands/all/x25519.go @@ -6,9 +6,9 @@ import ( var cmdX25519 = &base.Command{ UsageLine: `{{.Exec}} x25519 [-i "private key (base64.RawURLEncoding)"] [--std-encoding]`, - Short: `Generate key pair for x25519 key exchange`, + Short: `Generate key pair for X25519 key exchange (VLESS, REALITY)`, Long: ` -Generate key pair for x25519 key exchange. +Generate key pair for X25519 key exchange (VLESS, REALITY). Random: {{.Exec}} x25519 diff --git a/xray-core/proxy/http/server.go b/xray-core/proxy/http/server.go index 3721dd4712..8d6290a38e 100644 --- a/xray-core/proxy/http/server.go +++ b/xray-core/proxy/http/server.go @@ -23,6 +23,7 @@ import ( "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/routing" + "github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/transport/internet/stat" ) @@ -95,6 +96,9 @@ func (s *Server) ProcessWithFirstbyte(ctx context.Context, network net.Network, inbound.User = &protocol.MemoryUser{ Level: s.config.UserLevel, } + if !proxy.IsRAWTransport(conn) { + inbound.CanSpliceCopy = 3 + } var reader *bufio.Reader if len(firstbyte) > 0 { readerWithoutFirstbyte := bufio.NewReaderSize(readerOnly{conn}, buf.Size) @@ -207,7 +211,9 @@ func (s *Server) handleConnect(ctx context.Context, _ *http.Request, reader *buf } responseDone := func() error { - inbound.CanSpliceCopy = 1 + if inbound.CanSpliceCopy == 2 { + inbound.CanSpliceCopy = 1 + } defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) v2writer := buf.NewWriter(conn) diff --git a/xray-core/proxy/proxy.go b/xray-core/proxy/proxy.go index 3fec31af94..049d9fbdbb 100644 --- a/xray-core/proxy/proxy.go +++ b/xray-core/proxy/proxy.go @@ -25,6 +25,7 @@ import ( "github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/stats" + "github.com/xtls/xray-core/proxy/vless/encryption" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/reality" @@ -524,24 +525,33 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte } } -// UnwrapRawConn support unwrap stats, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it +// UnwrapRawConn support unwrap encryption, stats, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) { var readCounter, writerCounter stats.Counter if conn != nil { - statConn, ok := conn.(*stat.CounterConnection) - if ok { + isEncryption := false + if commonConn, ok := conn.(*encryption.CommonConn); ok { + conn = commonConn.Conn + isEncryption = true + } + if xorConn, ok := conn.(*encryption.XorConn); ok { + return xorConn, nil, nil // full-random xorConn should not be penetrated + } + if statConn, ok := conn.(*stat.CounterConnection); ok { conn = statConn.Connection readCounter = statConn.ReadCounter writerCounter = statConn.WriteCounter } - if xc, ok := conn.(*tls.Conn); ok { - conn = xc.NetConn() - } else if utlsConn, ok := conn.(*tls.UConn); ok { - conn = utlsConn.NetConn() - } else if realityConn, ok := conn.(*reality.Conn); ok { - conn = realityConn.NetConn() - } else if realityUConn, ok := conn.(*reality.UConn); ok { - conn = realityUConn.NetConn() + if !isEncryption { // avoids double penetration + if xc, ok := conn.(*tls.Conn); ok { + conn = xc.NetConn() + } else if utlsConn, ok := conn.(*tls.UConn); ok { + conn = utlsConn.NetConn() + } else if realityConn, ok := conn.(*reality.Conn); ok { + conn = realityConn.NetConn() + } else if realityUConn, ok := conn.(*reality.UConn); ok { + conn = realityUConn.NetConn() + } } if pc, ok := conn.(*proxyproto.Conn); ok { conn = pc.Raw() @@ -632,9 +642,20 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net } func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, readCounter stats.Counter) error { - errors.LogInfo(ctx, "CopyRawConn readv") + errors.LogInfo(ctx, "CopyRawConn (maybe) readv") if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil { return errors.New("failed to process response").Base(err) } return nil } + +func IsRAWTransport(conn stat.Connection) bool { + iConn := conn + if statConn, ok := iConn.(*stat.CounterConnection); ok { + iConn = statConn.Connection + } + _, ok1 := iConn.(*proxyproto.Conn) + _, ok2 := iConn.(*net.TCPConn) + _, ok3 := iConn.(*internet.UnixConnWrapper) + return ok1 || ok2 || ok3 +} diff --git a/xray-core/proxy/socks/server.go b/xray-core/proxy/socks/server.go index 8a159fad0b..08e2e6577d 100644 --- a/xray-core/proxy/socks/server.go +++ b/xray-core/proxy/socks/server.go @@ -19,6 +19,7 @@ import ( "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/routing" + "github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy/http" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/udp" @@ -75,6 +76,9 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con inbound.User = &protocol.MemoryUser{ Level: s.config.UserLevel, } + if !proxy.IsRAWTransport(conn) { + inbound.CanSpliceCopy = 3 + } switch network { case net.Network_TCP: @@ -199,7 +203,9 @@ func (s *Server) transport(ctx context.Context, reader io.Reader, writer io.Writ } responseDone := func() error { - inbound.CanSpliceCopy = 1 + if inbound.CanSpliceCopy == 2 { + inbound.CanSpliceCopy = 1 + } defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) v2writer := buf.NewWriter(writer) @@ -259,7 +265,9 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis if inbound != nil && inbound.Source.IsValid() { errors.LogInfo(ctx, "client UDP connection from ", inbound.Source) } - inbound.CanSpliceCopy = 1 + if inbound.CanSpliceCopy == 2 { + inbound.CanSpliceCopy = 1 + } var dest *net.Destination diff --git a/xray-core/proxy/vless/account.go b/xray-core/proxy/vless/account.go index c22cfe1620..b1e09619c2 100644 --- a/xray-core/proxy/vless/account.go +++ b/xray-core/proxy/vless/account.go @@ -18,6 +18,8 @@ func (a *Account) AsAccount() (protocol.Account, error) { ID: protocol.NewID(id), Flow: a.Flow, // needs parser here? Encryption: a.Encryption, // needs parser here? + XorMode: a.XorMode, + Seconds: a.Seconds, }, nil } @@ -27,8 +29,10 @@ type MemoryAccount struct { ID *protocol.ID // Flow of the account. May be "xtls-rprx-vision". Flow string - // Encryption of the account. Used for client connections, and only accepts "none" for now. + Encryption string + XorMode uint32 + Seconds uint32 } // Equals implements protocol.Account.Equals(). @@ -45,5 +49,7 @@ func (a *MemoryAccount) ToProto() proto.Message { Id: a.ID.String(), Flow: a.Flow, Encryption: a.Encryption, + XorMode: a.XorMode, + Seconds: a.Seconds, } } diff --git a/xray-core/proxy/vless/account.pb.go b/xray-core/proxy/vless/account.pb.go index fd5d451846..6048dc4e43 100644 --- a/xray-core/proxy/vless/account.pb.go +++ b/xray-core/proxy/vless/account.pb.go @@ -28,9 +28,10 @@ type Account struct { // ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // Flow settings. May be "xtls-rprx-vision". - Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"` - // Encryption settings. Only applies to client side, and only accepts "none" for now. + Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"` Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"` + XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"` + Seconds uint32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds,omitempty"` } func (x *Account) Reset() { @@ -84,23 +85,40 @@ func (x *Account) GetEncryption() string { return "" } +func (x *Account) GetXorMode() uint32 { + if x != nil { + return x.XorMode + } + return 0 +} + +func (x *Account) GetSeconds() uint32 { + if x != nil { + return x.Seconds + } + return 0 +} + var File_proxy_vless_account_proto protoreflect.FileDescriptor var file_proxy_vless_account_proto_rawDesc = []byte{ 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, - 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x4d, 0x0a, - 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, 0x0a, - 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x52, 0x0a, 0x14, - 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, - 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, - 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10, - 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x81, 0x01, + 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, + 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, + 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, + 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x73, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, + 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, + 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, + 0x73, 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, + 0x56, 0x6c, 0x65, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/xray-core/proxy/vless/account.proto b/xray-core/proxy/vless/account.proto index 51d2cb7dea..ebb1feff0f 100644 --- a/xray-core/proxy/vless/account.proto +++ b/xray-core/proxy/vless/account.proto @@ -11,6 +11,8 @@ message Account { string id = 1; // Flow settings. May be "xtls-rprx-vision". string flow = 2; - // Encryption settings. Only applies to client side, and only accepts "none" for now. + string encryption = 3; + uint32 xorMode = 4; + uint32 seconds = 5; } diff --git a/xray-core/proxy/vless/encoding/encoding.go b/xray-core/proxy/vless/encoding/encoding.go index 61bfb911b2..1176a96039 100644 --- a/xray-core/proxy/vless/encoding/encoding.go +++ b/xray-core/proxy/vless/encoding/encoding.go @@ -172,7 +172,7 @@ func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*A } // XtlsRead filter and read xtls protocol -func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error { +func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, peerCache *[]byte, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error { err := func() error { for { if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy { @@ -194,15 +194,21 @@ func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, if !buffer.IsEmpty() { timer.Update() if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy { - // XTLS Vision processes struct TLS Conn's input and rawInput - if inputBuffer, err := buf.ReadFrom(input); err == nil { - if !inputBuffer.IsEmpty() { - buffer, _ = buf.MergeMulti(buffer, inputBuffer) + // XTLS Vision processes struct Encryption Conn's peerCache or TLS Conn's input and rawInput + if peerCache != nil { + if len(*peerCache) != 0 { + buffer = buf.MergeBytes(buffer, *peerCache) } - } - if rawInputBuffer, err := buf.ReadFrom(rawInput); err == nil { - if !rawInputBuffer.IsEmpty() { - buffer, _ = buf.MergeMulti(buffer, rawInputBuffer) + } else { + if inputBuffer, err := buf.ReadFrom(input); err == nil { + if !inputBuffer.IsEmpty() { + buffer, _ = buf.MergeMulti(buffer, inputBuffer) + } + } + if rawInputBuffer, err := buf.ReadFrom(rawInput); err == nil { + if !rawInputBuffer.IsEmpty() { + buffer, _ = buf.MergeMulti(buffer, rawInputBuffer) + } } } } diff --git a/xray-core/proxy/vless/encryption/client.go b/xray-core/proxy/vless/encryption/client.go new file mode 100644 index 0000000000..301c23280d --- /dev/null +++ b/xray-core/proxy/vless/encryption/client.go @@ -0,0 +1,202 @@ +package encryption + +import ( + "crypto/cipher" + "crypto/ecdh" + "crypto/mlkem" + "crypto/rand" + "io" + "net" + "sync" + "time" + + "github.com/xtls/xray-core/common/crypto" + "github.com/xtls/xray-core/common/errors" + "lukechampine.com/blake3" +) + +type ClientInstance struct { + NfsPKeys []any + NfsPKeysBytes [][]byte + Hash32s [][32]byte + RelaysLength int + XorMode uint32 + Seconds uint32 + + RWLock sync.RWMutex + Expire time.Time + PfsKey []byte + Ticket []byte +} + +func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) (err error) { + if i.NfsPKeys != nil { + err = errors.New("already initialized") + return + } + l := len(nfsPKeysBytes) + if l == 0 { + err = errors.New("empty nfsPKeysBytes") + return + } + i.NfsPKeys = make([]any, l) + i.NfsPKeysBytes = nfsPKeysBytes + i.Hash32s = make([][32]byte, l) + for j, k := range nfsPKeysBytes { + if len(k) == 32 { + if i.NfsPKeys[j], err = ecdh.X25519().NewPublicKey(k); err != nil { + return + } + i.RelaysLength += 32 + 32 + } else { + if i.NfsPKeys[j], err = mlkem.NewEncapsulationKey768(k); err != nil { + return + } + i.RelaysLength += 1088 + 32 + } + i.Hash32s[j] = blake3.Sum256(k) + } + i.RelaysLength -= 32 + i.XorMode = xorMode + i.Seconds = seconds + return +} + +func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { + if i.NfsPKeys == nil { + return nil, errors.New("uninitialized") + } + c := NewCommonConn(conn) + + ivAndRealysLength := 16 + i.RelaysLength + pfsKeyExchangeLength := 18 + 1184 + 32 + 16 + paddingLength := int(crypto.RandBetween(100, 1000)) + clientHello := make([]byte, ivAndRealysLength+pfsKeyExchangeLength+paddingLength) + + iv := clientHello[:16] + rand.Read(iv) + relays := clientHello[16:ivAndRealysLength] + var nfsKey []byte + var lastCTR cipher.Stream + for j, k := range i.NfsPKeys { + var index = 32 + if k, ok := k.(*ecdh.PublicKey); ok { + privateKey, _ := ecdh.X25519().GenerateKey(rand.Reader) + copy(relays, privateKey.PublicKey().Bytes()) + var err error + nfsKey, err = privateKey.ECDH(k) + if err != nil { + return nil, err + } + } + if k, ok := k.(*mlkem.EncapsulationKey768); ok { + var ciphertext []byte + nfsKey, ciphertext = k.Encapsulate() + copy(relays, ciphertext) + index = 1088 + } + if i.XorMode > 0 { // this xor can (others can't) be recovered by client's config, revealing an X25519 public key / ML-KEM-768 ciphertext, that's why "native" values + NewCTR(i.NfsPKeysBytes[j], iv).XORKeyStream(relays, relays[:index]) // make X25519 public key / ML-KEM-768 ciphertext distinguishable from random bytes + } + if lastCTR != nil { + lastCTR.XORKeyStream(relays, relays[:32]) // make this relay irreplaceable + } + if j == len(i.NfsPKeys)-1 { + break + } + lastCTR = NewCTR(nfsKey, iv) + lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:]) + relays = relays[index+32:] + } + nfsGCM := NewGCM(iv, nfsKey) + + if i.Seconds > 0 { + i.RWLock.RLock() + if time.Now().Before(i.Expire) { + c.Client = i + c.UnitedKey = append(i.PfsKey, nfsKey...) // different unitedKey for each connection + nfsGCM.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil) + nfsGCM.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil) + i.RWLock.RUnlock() + c.PreWrite = clientHello[:ivAndRealysLength+18+32] + c.GCM = NewGCM(clientHello[ivAndRealysLength+18:ivAndRealysLength+18+32], c.UnitedKey) + if i.XorMode == 2 { + c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), nil, len(c.PreWrite), 16) + } + return c, nil + } + i.RWLock.RUnlock() + } + + pfsKeyExchange := clientHello[ivAndRealysLength : ivAndRealysLength+pfsKeyExchangeLength] + nfsGCM.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil) + mlkem768DKey, _ := mlkem.GenerateKey768() + x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader) + pfsPublicKey := append(mlkem768DKey.EncapsulationKey().Bytes(), x25519SKey.PublicKey().Bytes()...) + nfsGCM.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil) + + padding := clientHello[ivAndRealysLength+pfsKeyExchangeLength:] + nfsGCM.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) + nfsGCM.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) + + if _, err := conn.Write(clientHello); err != nil { + return nil, err + } + // padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control + + encryptedPfsPublicKey := make([]byte, 1088+32+16) + if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { + return nil, err + } + nfsGCM.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil) + mlkem768Key, err := mlkem768DKey.Decapsulate(encryptedPfsPublicKey[:1088]) + if err != nil { + return nil, err + } + peerX25519PKey, err := ecdh.X25519().NewPublicKey(encryptedPfsPublicKey[1088 : 1088+32]) + if err != nil { + return nil, err + } + x25519Key, err := x25519SKey.ECDH(peerX25519PKey) + if err != nil { + return nil, err + } + pfsKey := make([]byte, 32+32) // no more capacity + copy(pfsKey, mlkem768Key) + copy(pfsKey[32:], x25519Key) + c.UnitedKey = append(pfsKey, nfsKey...) + c.GCM = NewGCM(pfsPublicKey, c.UnitedKey) + c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1088+32], c.UnitedKey) + + encryptedTicket := make([]byte, 32) + if _, err := io.ReadFull(conn, encryptedTicket); err != nil { + return nil, err + } + if _, err := c.PeerGCM.Open(encryptedTicket[:0], nil, encryptedTicket, nil); err != nil { + return nil, err + } + seconds := DecodeLength(encryptedTicket) + + if i.Seconds > 0 && seconds > 0 { + i.RWLock.Lock() + i.Expire = time.Now().Add(time.Duration(seconds) * time.Second) + i.PfsKey = pfsKey + i.Ticket = encryptedTicket[:16] + i.RWLock.Unlock() + } + + encryptedLength := make([]byte, 18) + if _, err := io.ReadFull(conn, encryptedLength); err != nil { + return nil, err + } + if _, err := c.PeerGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil { + return nil, err + } + length := DecodeLength(encryptedLength[:2]) + c.PeerPadding = make([]byte, length) // important: allows server sends padding slowly, eliminating 1-RTT's traffic pattern + + if i.XorMode == 2 { + c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), NewCTR(c.UnitedKey, encryptedTicket[:16]), 0, length) + } + return c, nil +} diff --git a/xray-core/proxy/vless/encryption/common.go b/xray-core/proxy/vless/encryption/common.go new file mode 100644 index 0000000000..6f914c8db7 --- /dev/null +++ b/xray-core/proxy/vless/encryption/common.go @@ -0,0 +1,212 @@ +package encryption + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "fmt" + "io" + "net" + "strings" + "sync" + "time" + + "github.com/xtls/xray-core/common/errors" + "lukechampine.com/blake3" +) + +var OutBytesPool = sync.Pool{ + New: func() any { + return make([]byte, 5+8192+16) + }, +} + +type CommonConn struct { + net.Conn + Client *ClientInstance + UnitedKey []byte + PreWrite []byte + GCM *GCM + PeerGCM *GCM + PeerPadding []byte + PeerInBytes []byte + PeerCache []byte +} + +func NewCommonConn(conn net.Conn) *CommonConn { + return &CommonConn{ + Conn: conn, + PeerInBytes: make([]byte, 5+17000), // no need to use sync.Pool, because we are always reading + } +} + +func (c *CommonConn) Write(b []byte) (int, error) { + if len(b) == 0 { + return 0, nil + } + outBytes := OutBytesPool.Get().([]byte) + defer OutBytesPool.Put(outBytes) + for n := 0; n < len(b); { + b := b[n:] + if len(b) > 8192 { + b = b[:8192] // for avoiding another copy() in peer's Read() + } + n += len(b) + headerAndData := outBytes[:5+len(b)+16] + EncodeHeader(headerAndData, len(b)+16) + max := false + if bytes.Equal(c.GCM.Nonce[:], MaxNonce) { + max = true + } + c.GCM.Seal(headerAndData[:5], nil, b, headerAndData[:5]) + if max { + c.GCM = NewGCM(headerAndData, c.UnitedKey) + } + if c.PreWrite != nil { + headerAndData = append(c.PreWrite, headerAndData...) + c.PreWrite = nil + } + if _, err := c.Conn.Write(headerAndData); err != nil { + return 0, err + } + } + return len(b), nil +} + +func (c *CommonConn) Read(b []byte) (int, error) { + if len(b) == 0 { + return 0, nil + } + if c.PeerGCM == nil { // client's 0-RTT + serverRandom := make([]byte, 16) + if _, err := io.ReadFull(c.Conn, serverRandom); err != nil { + return 0, err + } + c.PeerGCM = NewGCM(serverRandom, c.UnitedKey) + if xorConn, ok := c.Conn.(*XorConn); ok { + xorConn.PeerCTR = NewCTR(c.UnitedKey, serverRandom) + } + } + if c.PeerPadding != nil { // client's 1-RTT + if _, err := io.ReadFull(c.Conn, c.PeerPadding); err != nil { + return 0, err + } + if _, err := c.PeerGCM.Open(c.PeerPadding[:0], nil, c.PeerPadding, nil); err != nil { + return 0, err + } + c.PeerPadding = nil + } + if len(c.PeerCache) > 0 { + n := copy(b, c.PeerCache) + c.PeerCache = c.PeerCache[n:] + return n, nil + } + peerHeader := c.PeerInBytes[:5] + if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { + return 0, err + } + l, err := DecodeHeader(c.PeerInBytes[:5]) // l: 17~17000 + if err != nil { + if c.Client != nil && strings.Contains(err.Error(), "invalid header: ") { // client's 0-RTT + c.Client.RWLock.Lock() + if bytes.HasPrefix(c.UnitedKey, c.Client.PfsKey) { + c.Client.Expire = time.Now() // expired + } + c.Client.RWLock.Unlock() + return 0, errors.New("new handshake needed") + } + return 0, err + } + c.Client = nil + peerData := c.PeerInBytes[5 : 5+l] + if _, err := io.ReadFull(c.Conn, peerData); err != nil { + return 0, err + } + dst := peerData[:l-16] + if len(dst) <= len(b) { + dst = b[:len(dst)] // avoids another copy() + } + var newGCM *GCM + if bytes.Equal(c.PeerGCM.Nonce[:], MaxNonce) { + newGCM = NewGCM(c.PeerInBytes[:5+l], c.UnitedKey) + } + _, err = c.PeerGCM.Open(dst[:0], nil, peerData, peerHeader) + if newGCM != nil { + c.PeerGCM = newGCM + } + if err != nil { + return 0, err + } + if len(dst) > len(b) { + c.PeerCache = dst[copy(b, dst):] + dst = b // for len(dst) + } + return len(dst), nil +} + +type GCM struct { + cipher.AEAD + Nonce [12]byte +} + +func NewGCM(ctx, key []byte) *GCM { + k := make([]byte, 32) + blake3.DeriveKey(k, string(ctx), key) + block, _ := aes.NewCipher(k) + aead, _ := cipher.NewGCM(block) + return &GCM{AEAD: aead} + //chacha20poly1305.New() +} + +func (a *GCM) Seal(dst, nonce, plaintext, additionalData []byte) []byte { + if nonce == nil { + nonce = IncreaseNonce(a.Nonce[:]) + } + return a.AEAD.Seal(dst, nonce, plaintext, additionalData) +} + +func (a *GCM) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { + if nonce == nil { + nonce = IncreaseNonce(a.Nonce[:]) + } + return a.AEAD.Open(dst, nonce, ciphertext, additionalData) +} + +func IncreaseNonce(nonce []byte) []byte { + for i := range 12 { + nonce[11-i]++ + if nonce[11-i] != 0 { + break + } + } + return nonce +} + +var MaxNonce = bytes.Repeat([]byte{255}, 12) + +func EncodeLength(l int) []byte { + return []byte{byte(l >> 8), byte(l)} +} + +func DecodeLength(b []byte) int { + return int(b[0])<<8 | int(b[1]) +} + +func EncodeHeader(h []byte, l int) { + h[0] = 23 + h[1] = 3 + h[2] = 3 + h[3] = byte(l >> 8) + h[4] = byte(l) +} + +func DecodeHeader(h []byte) (l int, err error) { + l = int(h[3])<<8 | int(h[4]) + if h[0] != 23 || h[1] != 3 || h[2] != 3 { + l = 0 + } + if l < 17 || l > 17000 { // TODO: TLSv1.3 max length + err = errors.New("invalid header: ", fmt.Sprintf("%v", h[:5])) // DO NOT CHANGE: relied by client's Read() + } + return +} diff --git a/xray-core/proxy/vless/encryption/server.go b/xray-core/proxy/vless/encryption/server.go new file mode 100644 index 0000000000..8594fd2527 --- /dev/null +++ b/xray-core/proxy/vless/encryption/server.go @@ -0,0 +1,282 @@ +package encryption + +import ( + "bytes" + "crypto/cipher" + "crypto/ecdh" + "crypto/mlkem" + "crypto/rand" + "fmt" + "io" + "net" + "sync" + "time" + + "github.com/xtls/xray-core/common/crypto" + "github.com/xtls/xray-core/common/errors" + "lukechampine.com/blake3" +) + +type ServerSession struct { + Expire time.Time + PfsKey []byte + NfsKeys sync.Map +} + +type ServerInstance struct { + NfsSKeys []any + NfsPKeysBytes [][]byte + Hash32s [][32]byte + RelaysLength int + XorMode uint32 + Seconds uint32 + + RWLock sync.RWMutex + Sessions map[[16]byte]*ServerSession + Closed bool +} + +func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (err error) { + if i.NfsSKeys != nil { + err = errors.New("already initialized") + return + } + l := len(nfsSKeysBytes) + if l == 0 { + err = errors.New("empty nfsSKeysBytes") + return + } + i.NfsSKeys = make([]any, l) + i.NfsPKeysBytes = make([][]byte, l) + i.Hash32s = make([][32]byte, l) + for j, k := range nfsSKeysBytes { + if len(k) == 32 { + if i.NfsSKeys[j], err = ecdh.X25519().NewPrivateKey(k); err != nil { + return + } + i.NfsPKeysBytes[j] = i.NfsSKeys[j].(*ecdh.PrivateKey).PublicKey().Bytes() + i.RelaysLength += 32 + 32 + } else { + if i.NfsSKeys[j], err = mlkem.NewDecapsulationKey768(k); err != nil { + return + } + i.NfsPKeysBytes[j] = i.NfsSKeys[j].(*mlkem.DecapsulationKey768).EncapsulationKey().Bytes() + i.RelaysLength += 1088 + 32 + } + i.Hash32s[j] = blake3.Sum256(i.NfsPKeysBytes[j]) + } + i.RelaysLength -= 32 + i.XorMode = xorMode + if seconds > 0 { + i.Seconds = seconds + i.Sessions = make(map[[16]byte]*ServerSession) + go func() { + for { + time.Sleep(time.Minute) + i.RWLock.Lock() + if i.Closed { + i.RWLock.Unlock() + return + } + now := time.Now() + for ticket, session := range i.Sessions { + if now.After(session.Expire) { + delete(i.Sessions, ticket) + } + } + i.RWLock.Unlock() + } + }() + } + return +} + +func (i *ServerInstance) Close() (err error) { + i.RWLock.Lock() + i.Closed = true + i.RWLock.Unlock() + return +} + +func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { + if i.NfsSKeys == nil { + return nil, errors.New("uninitialized") + } + c := NewCommonConn(conn) + + ivAndRelays := make([]byte, 16+i.RelaysLength) + if _, err := io.ReadFull(conn, ivAndRelays); err != nil { + return nil, err + } + iv := ivAndRelays[:16] + relays := ivAndRelays[16:] + var nfsKey []byte + var lastCTR cipher.Stream + for j, k := range i.NfsSKeys { + if lastCTR != nil { + lastCTR.XORKeyStream(relays, relays[:32]) // recover this relay + } + var index = 32 + if _, ok := k.(*mlkem.DecapsulationKey768); ok { + index = 1088 + } + if i.XorMode > 0 { + NewCTR(i.NfsPKeysBytes[j], iv).XORKeyStream(relays, relays[:index]) // we don't use buggy elligator, because we have PSK :) + } + if k, ok := k.(*ecdh.PrivateKey); ok { + publicKey, err := ecdh.X25519().NewPublicKey(relays[:index]) + if err != nil { + return nil, err + } + nfsKey, err = k.ECDH(publicKey) + if err != nil { + return nil, err + } + } + if k, ok := k.(*mlkem.DecapsulationKey768); ok { + var err error + nfsKey, err = k.Decapsulate(relays[:index]) + if err != nil { + return nil, err + } + } + if j == len(i.NfsSKeys)-1 { + break + } + relays = relays[index:] + lastCTR = NewCTR(nfsKey, iv) + lastCTR.XORKeyStream(relays, relays[:32]) + if !bytes.Equal(relays[:32], i.Hash32s[j+1][:]) { + return nil, errors.New("unexpected hash32: ", fmt.Sprintf("%v", relays[:32])) + } + relays = relays[32:] + } + nfsGCM := NewGCM(iv, nfsKey) + + encryptedLength := make([]byte, 18) + if _, err := io.ReadFull(conn, encryptedLength); err != nil { + return nil, err + } + if _, err := nfsGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil { + return nil, err + } + length := DecodeLength(encryptedLength[:2]) + + if length == 32 { + if i.Seconds == 0 { + return nil, errors.New("0-RTT is not allowed") + } + encryptedTicket := make([]byte, 32) + if _, err := io.ReadFull(conn, encryptedTicket); err != nil { + return nil, err + } + ticket, err := nfsGCM.Open(nil, nil, encryptedTicket, nil) + if err != nil { + return nil, err + } + i.RWLock.RLock() + s := i.Sessions[[16]byte(ticket)] + i.RWLock.RUnlock() + if s == nil { + noises := make([]byte, crypto.RandBetween(1268, 2268)) // matches 1-RTT's server hello length for "random", though it is not important, just for example + var err error + for err == nil { + rand.Read(noises) + _, err = DecodeHeader(noises) + } + conn.Write(noises) // make client do new handshake + return nil, errors.New("expired ticket") + } + if _, loaded := s.NfsKeys.LoadOrStore([32]byte(nfsKey), true); loaded { // prevents bad client also + return nil, errors.New("replay detected") + } + c.UnitedKey = append(s.PfsKey, nfsKey...) // the same nfsKey links the upload & download (prevents server -> client's another request) + c.PreWrite = make([]byte, 16) + rand.Read(c.PreWrite) // always trust yourself, not the client (also prevents being parsed as TLS thus causing false interruption for "native" and "xorpub") + c.GCM = NewGCM(c.PreWrite, c.UnitedKey) + c.PeerGCM = NewGCM(encryptedTicket, c.UnitedKey) // unchangeable ctx (prevents server -> server), and different ctx length for upload / download (prevents client -> client) + if i.XorMode == 2 { + c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, c.PreWrite), NewCTR(c.UnitedKey, iv), 16, 0) // it doesn't matter if the attacker sends client's iv back to the client + } + return c, nil + } + + if length < 1184+32+16 { // client may send more public keys in the future's version + return nil, errors.New("too short length") + } + encryptedPfsPublicKey := make([]byte, length) + if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { + return nil, err + } + if _, err := nfsGCM.Open(encryptedPfsPublicKey[:0], nil, encryptedPfsPublicKey, nil); err != nil { + return nil, err + } + mlkem768EKey, err := mlkem.NewEncapsulationKey768(encryptedPfsPublicKey[:1184]) + if err != nil { + return nil, err + } + mlkem768Key, encapsulatedPfsKey := mlkem768EKey.Encapsulate() + peerX25519PKey, err := ecdh.X25519().NewPublicKey(encryptedPfsPublicKey[1184 : 1184+32]) + if err != nil { + return nil, err + } + x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader) + x25519Key, err := x25519SKey.ECDH(peerX25519PKey) + if err != nil { + return nil, err + } + pfsKey := make([]byte, 32+32) // no more capacity + copy(pfsKey, mlkem768Key) + copy(pfsKey[32:], x25519Key) + pfsPublicKey := append(encapsulatedPfsKey, x25519SKey.PublicKey().Bytes()...) + c.UnitedKey = append(pfsKey, nfsKey...) + c.GCM = NewGCM(pfsPublicKey, c.UnitedKey) + c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1184+32], c.UnitedKey) + ticket := make([]byte, 16) + rand.Read(ticket) + copy(ticket, EncodeLength(int(i.Seconds*4/5))) + + pfsKeyExchangeLength := 1088 + 32 + 16 + encryptedTicketLength := 32 + paddingLength := int(crypto.RandBetween(100, 1000)) + serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength) + nfsGCM.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil) + c.GCM.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil) + padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:] + c.GCM.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) + c.GCM.Seal(padding[:18], nil, padding[18:paddingLength-16], nil) + + if _, err := conn.Write(serverHello); err != nil { + return nil, err + } + // padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control + + if i.Seconds > 0 { + i.RWLock.Lock() + i.Sessions[[16]byte(ticket)] = &ServerSession{ + Expire: time.Now().Add(time.Duration(i.Seconds) * time.Second), + PfsKey: pfsKey, + } + i.RWLock.Unlock() + } + + // important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern + if _, err := io.ReadFull(conn, encryptedLength); err != nil { + return nil, err + } + if _, err := nfsGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil { + return nil, err + } + encryptedPadding := make([]byte, DecodeLength(encryptedLength[:2])) + if _, err := io.ReadFull(conn, encryptedPadding); err != nil { + return nil, err + } + if _, err := nfsGCM.Open(encryptedPadding[:0], nil, encryptedPadding, nil); err != nil { + return nil, err + } + + if i.XorMode == 2 { + c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, ticket), NewCTR(c.UnitedKey, iv), 0, 0) + } + return c, nil +} diff --git a/xray-core/proxy/vless/encryption/xor.go b/xray-core/proxy/vless/encryption/xor.go new file mode 100644 index 0000000000..e435cb5ce8 --- /dev/null +++ b/xray-core/proxy/vless/encryption/xor.go @@ -0,0 +1,93 @@ +package encryption + +import ( + "crypto/aes" + "crypto/cipher" + "net" + + "lukechampine.com/blake3" +) + +func NewCTR(key, iv []byte) cipher.Stream { + k := make([]byte, 32) + blake3.DeriveKey(k, "VLESS", key) // avoids using key directly + block, _ := aes.NewCipher(k) + return cipher.NewCTR(block, iv) + //chacha20.NewUnauthenticatedCipher() +} + +type XorConn struct { + net.Conn + CTR cipher.Stream + PeerCTR cipher.Stream + OutSkip int + OutHeader []byte + InSkip int + InHeader []byte +} + +func NewXorConn(conn net.Conn, ctr, peerCTR cipher.Stream, outSkip, inSkip int) *XorConn { + return &XorConn{ + Conn: conn, + CTR: ctr, + PeerCTR: peerCTR, + OutSkip: outSkip, + OutHeader: make([]byte, 0, 5), // important + InSkip: inSkip, + InHeader: make([]byte, 0, 5), // important + } +} + +func (c *XorConn) Write(b []byte) (int, error) { + if len(b) == 0 { + return 0, nil + } + for p := b; ; { + if len(p) <= c.OutSkip { + c.OutSkip -= len(p) + break + } + p = p[c.OutSkip:] + c.OutSkip = 0 + need := 5 - len(c.OutHeader) + if len(p) < need { + c.OutHeader = append(c.OutHeader, p...) + c.CTR.XORKeyStream(p, p) + break + } + c.OutSkip, _ = DecodeHeader(append(c.OutHeader, p[:need]...)) + c.OutHeader = c.OutHeader[:0] + c.CTR.XORKeyStream(p[:need], p[:need]) + p = p[need:] + } + if _, err := c.Conn.Write(b); err != nil { + return 0, err + } + return len(b), nil +} + +func (c *XorConn) Read(b []byte) (int, error) { + if len(b) == 0 { + return 0, nil + } + n, err := c.Conn.Read(b) + for p := b[:n]; ; { + if len(p) <= c.InSkip { + c.InSkip -= len(p) + break + } + p = p[c.InSkip:] + c.InSkip = 0 + need := 5 - len(c.InHeader) + if len(p) < need { + c.PeerCTR.XORKeyStream(p, p) + c.InHeader = append(c.InHeader, p...) + break + } + c.PeerCTR.XORKeyStream(p[:need], p[:need]) + c.InSkip, _ = DecodeHeader(append(c.InHeader, p[:need]...)) + c.InHeader = c.InHeader[:0] + p = p[need:] + } + return n, err +} diff --git a/xray-core/proxy/vless/inbound/config.pb.go b/xray-core/proxy/vless/inbound/config.pb.go index 907a3f7f39..e3192cf8ec 100644 --- a/xray-core/proxy/vless/inbound/config.pb.go +++ b/xray-core/proxy/vless/inbound/config.pb.go @@ -111,11 +111,11 @@ type Config struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"` - // Decryption settings. Only applies to server side, and only accepts "none" - // for now. - Decryption string `protobuf:"bytes,2,opt,name=decryption,proto3" json:"decryption,omitempty"` - Fallbacks []*Fallback `protobuf:"bytes,3,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"` + Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"` + Fallbacks []*Fallback `protobuf:"bytes,2,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"` + Decryption string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,omitempty"` + XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"` + Seconds uint32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds,omitempty"` } func (x *Config) Reset() { @@ -155,6 +155,13 @@ func (x *Config) GetClients() []*protocol.User { return nil } +func (x *Config) GetFallbacks() []*Fallback { + if x != nil { + return x.Fallbacks + } + return nil +} + func (x *Config) GetDecryption() string { if x != nil { return x.Decryption @@ -162,11 +169,18 @@ func (x *Config) GetDecryption() string { return "" } -func (x *Config) GetFallbacks() []*Fallback { +func (x *Config) GetXorMode() uint32 { if x != nil { - return x.Fallbacks + return x.XorMode } - return nil + return 0 +} + +func (x *Config) GetSeconds() uint32 { + if x != nil { + return x.Seconds + } + return 0 } var File_proxy_vless_inbound_config_proto protoreflect.FileDescriptor @@ -185,25 +199,28 @@ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{ 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65, - 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xa0, 0x01, + 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xd4, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, - 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1e, - 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40, - 0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x40, + 0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, - 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, - 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, - 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, - 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, - 0x64, 0xaa, 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, - 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x18, 0x0a, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63, + 0x6f, 0x6e, 0x64, 0x73, 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, + 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, + 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, + 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/xray-core/proxy/vless/inbound/config.proto b/xray-core/proxy/vless/inbound/config.proto index 94b5551c54..e1ebc8d370 100644 --- a/xray-core/proxy/vless/inbound/config.proto +++ b/xray-core/proxy/vless/inbound/config.proto @@ -19,8 +19,9 @@ message Fallback { message Config { repeated xray.common.protocol.User clients = 1; - // Decryption settings. Only applies to server side, and only accepts "none" - // for now. - string decryption = 2; - repeated Fallback fallbacks = 3; + repeated Fallback fallbacks = 2; + + string decryption = 3; + uint32 xorMode = 4; + uint32 seconds = 5; } diff --git a/xray-core/proxy/vless/inbound/inbound.go b/xray-core/proxy/vless/inbound/inbound.go index dfa8d470aa..ca5176b928 100644 --- a/xray-core/proxy/vless/inbound/inbound.go +++ b/xray-core/proxy/vless/inbound/inbound.go @@ -4,6 +4,7 @@ import ( "bytes" "context" gotls "crypto/tls" + "encoding/base64" "io" "reflect" "strconv" @@ -29,6 +30,7 @@ import ( "github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy/vless" "github.com/xtls/xray-core/proxy/vless/encoding" + "github.com/xtls/xray-core/proxy/vless/encryption" "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" @@ -67,6 +69,7 @@ type Handler struct { policyManager policy.Manager validator vless.Validator dns dns.Client + decryption *encryption.ServerInstance fallbacks map[string]map[string]map[string]*Fallback // or nil // regexps map[string]*regexp.Regexp // or nil } @@ -81,6 +84,19 @@ func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Val validator: validator, } + if config.Decryption != "" && config.Decryption != "none" { + s := strings.Split(config.Decryption, ".") + var nfsSKeysBytes [][]byte + for _, r := range s { + b, _ := base64.RawURLEncoding.DecodeString(r) + nfsSKeysBytes = append(nfsSKeysBytes, b) + } + handler.decryption = &encryption.ServerInstance{} + if err := handler.decryption.Init(nfsSKeysBytes, config.XorMode, config.Seconds); err != nil { + return nil, errors.New("failed to use decryption").Base(err).AtError() + } + } + if config.Fallbacks != nil { handler.fallbacks = make(map[string]map[string]map[string]*Fallback) // handler.regexps = make(map[string]*regexp.Regexp) @@ -159,6 +175,9 @@ func isMuxAndNotXUDP(request *protocol.RequestHeader, first *buf.Buffer) bool { // Close implements common.Closable.Close(). func (h *Handler) Close() error { + if h.decryption != nil { + h.decryption.Close() + } return errors.Combine(common.Close(h.validator)) } @@ -199,6 +218,14 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s iConn = statConn.Connection } + if h.decryption != nil { + var err error + connection, err = h.decryption.Handshake(connection) + if err != nil { + return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo() + } + } + sessionPolicy := h.policyManager.ForLevel(0) if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil { return errors.New("unable to set read deadline").Base(err).AtWarning() @@ -464,6 +491,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s // Flow: requestAddons.Flow, } + var peerCache *[]byte var input *bytes.Reader var rawInput *bytes.Buffer switch requestAddons.Flow { @@ -476,6 +504,13 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s case protocol.RequestCommandMux: fallthrough // we will break Mux connections that contain TCP requests case protocol.RequestCommandTCP: + if serverConn, ok := connection.(*encryption.CommonConn); ok { + peerCache = &serverConn.PeerCache + if _, ok := serverConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransport(iConn) { + inbound.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice + } + break + } var t reflect.Type var p uintptr if tlsConn, ok := iConn.(*tls.Conn); ok { @@ -544,7 +579,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s if requestAddons.Flow == vless.XRV { ctx1 := session.ContextWithInbound(ctx, nil) // TODO enable splice clientReader = proxy.NewVisionReader(clientReader, trafficState, true, ctx1) - err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, input, rawInput, trafficState, nil, true, ctx1) + err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, peerCache, input, rawInput, trafficState, nil, true, ctx1) } else { // from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer)) diff --git a/xray-core/proxy/vless/outbound/outbound.go b/xray-core/proxy/vless/outbound/outbound.go index e1a727eb12..ee1b6dfb4c 100644 --- a/xray-core/proxy/vless/outbound/outbound.go +++ b/xray-core/proxy/vless/outbound/outbound.go @@ -4,7 +4,9 @@ import ( "bytes" "context" gotls "crypto/tls" + "encoding/base64" "reflect" + "strings" "time" "unsafe" @@ -24,6 +26,7 @@ import ( "github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy/vless" "github.com/xtls/xray-core/proxy/vless/encoding" + "github.com/xtls/xray-core/proxy/vless/encryption" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/reality" @@ -43,6 +46,7 @@ type Handler struct { serverPicker protocol.ServerPicker policyManager policy.Manager cone bool + encryption *encryption.ClientInstance } // New creates a new VLess outbound handler. @@ -64,6 +68,20 @@ func New(ctx context.Context, config *Config) (*Handler, error) { cone: ctx.Value("cone").(bool), } + a := handler.serverPicker.PickServer().PickUser().Account.(*vless.MemoryAccount) + if a.Encryption != "" && a.Encryption != "none" { + s := strings.Split(a.Encryption, ".") + var nfsPKeysBytes [][]byte + for _, r := range s { + b, _ := base64.RawURLEncoding.DecodeString(r) + nfsPKeysBytes = append(nfsPKeysBytes, b) + } + handler.encryption = &encryption.ClientInstance{} + if err := handler.encryption.Init(nfsPKeysBytes, a.XorMode, a.Seconds); err != nil { + return nil, errors.New("failed to use encryption").Base(err).AtError() + } + } + return handler, nil } @@ -98,6 +116,14 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte target := ob.Target errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination().NetAddr()) + if h.encryption != nil { + var err error + conn, err = h.encryption.Handshake(conn) + if err != nil { + return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo() + } + } + command := protocol.RequestCommandTCP if target.Network == net.Network_UDP { command = protocol.RequestCommandUDP @@ -120,6 +146,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte Flow: account.Flow, } + var peerCache *[]byte var input *bytes.Reader var rawInput *bytes.Buffer allowUDP443 := false @@ -138,6 +165,13 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte case protocol.RequestCommandMux: fallthrough // let server break Mux connections that contain TCP requests case protocol.RequestCommandTCP: + if clientConn, ok := conn.(*encryption.CommonConn); ok { + peerCache = &clientConn.PeerCache + if _, ok := clientConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransport(iConn) { + ob.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice + } + break + } var t reflect.Type var p uintptr if tlsConn, ok := iConn.(*tls.Conn); ok { @@ -272,7 +306,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } if requestAddons.Flow == vless.XRV { - err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, input, rawInput, trafficState, ob, false, ctx) + err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, peerCache, input, rawInput, trafficState, ob, false, ctx) } else { // from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer)) diff --git a/yt-dlp/Changelog.md b/yt-dlp/Changelog.md index d4ac4a5a69..2ad6da30e1 100644 --- a/yt-dlp/Changelog.md +++ b/yt-dlp/Changelog.md @@ -4,6 +4,19 @@ # To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master --> +### 2025.08.27 + +#### Extractor changes +- **generic** + - [Simplify invalid URL error message](https://github.com/yt-dlp/yt-dlp/commit/1ddbd033f0fd65917526b1271cea66913ac8647f) ([#14167](https://github.com/yt-dlp/yt-dlp/issues/14167)) by [seproDev](https://github.com/seproDev) + - [Use https as fallback protocol](https://github.com/yt-dlp/yt-dlp/commit/fec30c56f0e97e573ace659104ff0d72c4cc9809) ([#14160](https://github.com/yt-dlp/yt-dlp/issues/14160)) by [seproDev](https://github.com/seproDev) +- **skeb**: [Support wav files](https://github.com/yt-dlp/yt-dlp/commit/d6950c27af31908363c5c815e3b7eb4f9ff41643) ([#14147](https://github.com/yt-dlp/yt-dlp/issues/14147)) by [seproDev](https://github.com/seproDev) +- **youtube** + - [Add `tcc` player JS variant](https://github.com/yt-dlp/yt-dlp/commit/8f4a908300f55054bc96814bceeaa1034fdf4110) ([#14134](https://github.com/yt-dlp/yt-dlp/issues/14134)) by [bashonly](https://github.com/bashonly) + - [Deprioritize `web_safari` m3u8 formats](https://github.com/yt-dlp/yt-dlp/commit/5c7ad68ff1643ad80d18cef8be9db8fcab05ee6c) ([#14168](https://github.com/yt-dlp/yt-dlp/issues/14168)) by [bashonly](https://github.com/bashonly) + - [Player client maintenance](https://github.com/yt-dlp/yt-dlp/commit/3bd91544122142a87863d79e54e995c26cfd7f92) ([#14135](https://github.com/yt-dlp/yt-dlp/issues/14135)) by [bashonly](https://github.com/bashonly) + - [Use alternative `tv` user-agent when authenticated](https://github.com/yt-dlp/yt-dlp/commit/8cd37b85d492edb56a4f7506ea05527b85a6b02b) ([#14169](https://github.com/yt-dlp/yt-dlp/issues/14169)) by [bashonly](https://github.com/bashonly) + ### 2025.08.22 #### Core changes diff --git a/yt-dlp/yt_dlp/extractor/generic.py b/yt-dlp/yt_dlp/extractor/generic.py index b3a27f31e8..d44e6d3c4b 100644 --- a/yt-dlp/yt_dlp/extractor/generic.py +++ b/yt-dlp/yt_dlp/extractor/generic.py @@ -772,8 +772,8 @@ class GenericIE(InfoExtractor): if default_search in ('auto', 'auto_warning', 'fixup_error'): if re.match(r'[^\s/]+\.[^\s/]+/', url): - self.report_warning('The url doesn\'t specify the protocol, trying with http') - return self.url_result('http://' + url) + self.report_warning('The url doesn\'t specify the protocol, trying with https') + return self.url_result('https://' + url) elif default_search != 'fixup_error': if default_search == 'auto_warning': if re.match(r'^(?:url|URL)$', url): @@ -786,9 +786,7 @@ class GenericIE(InfoExtractor): return self.url_result('ytsearch:' + url) if default_search in ('error', 'fixup_error'): - raise ExtractorError( - f'{url!r} is not a valid URL. ' - f'Set --default-search "ytsearch" (or run yt-dlp "ytsearch:{url}" ) to search YouTube', expected=True) + raise ExtractorError(f'{url!r} is not a valid URL', expected=True) else: if ':' not in default_search: default_search += ':' diff --git a/yt-dlp/yt_dlp/extractor/googledrive.py b/yt-dlp/yt_dlp/extractor/googledrive.py index dfba2d3ba1..0c84f0b241 100644 --- a/yt-dlp/yt_dlp/extractor/googledrive.py +++ b/yt-dlp/yt_dlp/extractor/googledrive.py @@ -12,6 +12,7 @@ from ..utils import ( get_element_html_by_id, int_or_none, lowercase_escape, + parse_qs, try_get, update_url_query, ) @@ -111,14 +112,18 @@ class GoogleDriveIE(InfoExtractor): self._caption_formats_ext.append(f.attrib['fmt_code']) def _get_captions_by_type(self, video_id, subtitles_id, caption_type, - origin_lang_code=None): + origin_lang_code=None, origin_lang_name=None): if not subtitles_id or not caption_type: return captions = {} for caption_entry in self._captions_xml.findall( self._CAPTIONS_ENTRY_TAG[caption_type]): caption_lang_code = caption_entry.attrib.get('lang_code') - if not caption_lang_code: + caption_name = caption_entry.attrib.get('name') or origin_lang_name + if not caption_lang_code or not caption_name: + self.report_warning(f'Missing necessary caption metadata. ' + f'Need lang_code and name attributes. ' + f'Found: {caption_entry.attrib}') continue caption_format_data = [] for caption_format in self._caption_formats_ext: @@ -129,7 +134,7 @@ class GoogleDriveIE(InfoExtractor): 'lang': (caption_lang_code if origin_lang_code is None else origin_lang_code), 'type': 'track', - 'name': '', + 'name': caption_name, 'kind': '', } if origin_lang_code is not None: @@ -155,14 +160,15 @@ class GoogleDriveIE(InfoExtractor): self._download_subtitles_xml(video_id, subtitles_id, hl) if not self._captions_xml: return - track = self._captions_xml.find('track') + track = next((t for t in self._captions_xml.findall('track') if t.attrib.get('cantran') == 'true'), None) if track is None: return origin_lang_code = track.attrib.get('lang_code') - if not origin_lang_code: + origin_lang_name = track.attrib.get('name') + if not origin_lang_code or not origin_lang_name: return return self._get_captions_by_type( - video_id, subtitles_id, 'automatic_captions', origin_lang_code) + video_id, subtitles_id, 'automatic_captions', origin_lang_code, origin_lang_name) def _real_extract(self, url): video_id = self._match_id(url) @@ -268,10 +274,8 @@ class GoogleDriveIE(InfoExtractor): subtitles_id = None ttsurl = get_value('ttsurl') if ttsurl: - # the video Id for subtitles will be the last value in the ttsurl - # query string - subtitles_id = ttsurl.encode().decode( - 'unicode_escape').split('=')[-1] + # the subtitles ID is the vid param of the ttsurl query + subtitles_id = parse_qs(ttsurl).get('vid', [None])[-1] self.cookiejar.clear(domain='.google.com', path='/', name='NID') diff --git a/yt-dlp/yt_dlp/extractor/itv.py b/yt-dlp/yt_dlp/extractor/itv.py index 89e6f189cb..1f4020847c 100644 --- a/yt-dlp/yt_dlp/extractor/itv.py +++ b/yt-dlp/yt_dlp/extractor/itv.py @@ -18,6 +18,7 @@ from ..utils import ( url_or_none, urljoin, ) +from ..utils.traversal import traverse_obj class ITVIE(InfoExtractor): @@ -223,6 +224,7 @@ class ITVBTCCIE(InfoExtractor): }, 'playlist_count': 12, }, { + # news page, can have absent `data` field 'url': 'https://www.itv.com/news/2021-10-27/i-have-to-protect-the-country-says-rishi-sunak-as-uk-faces-interest-rate-hike', 'info_dict': { 'id': 'i-have-to-protect-the-country-says-rishi-sunak-as-uk-faces-interest-rate-hike', @@ -243,7 +245,7 @@ class ITVBTCCIE(InfoExtractor): entries = [] for video in json_map: - if not any(video['data'].get(attr) == 'Brightcove' for attr in ('name', 'type')): + if not any(traverse_obj(video, ('data', attr)) == 'Brightcove' for attr in ('name', 'type')): continue video_id = video['data']['id'] account_id = video['data']['accountId'] diff --git a/yt-dlp/yt_dlp/extractor/kick.py b/yt-dlp/yt_dlp/extractor/kick.py index 8049e1e342..06bd8bf591 100644 --- a/yt-dlp/yt_dlp/extractor/kick.py +++ b/yt-dlp/yt_dlp/extractor/kick.py @@ -95,26 +95,47 @@ class KickVODIE(KickBaseIE): IE_NAME = 'kick:vod' _VALID_URL = r'https?://(?:www\.)?kick\.com/[\w-]+/videos/(?P[\da-f]{8}-(?:[\da-f]{4}-){3}[\da-f]{12})' _TESTS = [{ - 'url': 'https://kick.com/xqc/videos/8dd97a8d-e17f-48fb-8bc3-565f88dbc9ea', - 'md5': '3870f94153e40e7121a6e46c068b70cb', + # Regular VOD + 'url': 'https://kick.com/xqc/videos/5c697a87-afce-4256-b01f-3c8fe71ef5cb', 'info_dict': { - 'id': '8dd97a8d-e17f-48fb-8bc3-565f88dbc9ea', + 'id': '5c697a87-afce-4256-b01f-3c8fe71ef5cb', 'ext': 'mp4', - 'title': '18+ #ad 🛑LIVE🛑CLICK🛑DRAMA🛑NEWS🛑STUFF🛑REACT🛑GET IN HHERE🛑BOP BOP🛑WEEEE WOOOO🛑', + 'title': '🐗LIVE🐗CLICK🐗HERE🐗DRAMA🐗ALL DAY🐗NEWS🐗VIDEOS🐗CLIPS🐗GAMES🐗STUFF🐗WOW🐗IM HERE🐗LETS GO🐗COOL🐗VERY NICE🐗', 'description': 'THE BEST AT ABSOLUTELY EVERYTHING. THE JUICER. LEADER OF THE JUICERS.', - 'channel': 'xqc', - 'channel_id': '668', 'uploader': 'xQc', 'uploader_id': '676', - 'upload_date': '20240909', - 'timestamp': 1725919141, - 'duration': 10155.0, - 'thumbnail': r're:^https?://.*\.jpg', + 'channel': 'xqc', + 'channel_id': '668', 'view_count': int, - 'categories': ['Just Chatting'], - 'age_limit': 0, + 'age_limit': 18, + 'duration': 22278.0, + 'thumbnail': r're:^https?://.*\.jpg', + 'categories': ['Deadlock'], + 'timestamp': 1756082443, + 'upload_date': '20250825', }, 'params': {'skip_download': 'm3u8'}, + }, { + # VOD of ongoing livestream (at the time of writing the test, ID rotates every two days) + 'url': 'https://kick.com/a-log-burner/videos/5230df84-ea38-46e1-be4f-f5949ae55641', + 'info_dict': { + 'id': '5230df84-ea38-46e1-be4f-f5949ae55641', + 'ext': 'mp4', + 'title': r're:😴 Cozy Fireplace ASMR 🔥 | Relax, Focus, Sleep 💤', + 'description': 'md5:080bc713eac0321a7b376a1b53816d1b', + 'uploader': 'A_Log_Burner', + 'uploader_id': '65114691', + 'channel': 'a-log-burner', + 'channel_id': '63967687', + 'view_count': int, + 'age_limit': 18, + 'thumbnail': r're:^https?://.*\.jpg', + 'categories': ['Other, Watch Party'], + 'timestamp': int, + 'upload_date': str, + 'live_status': 'is_live', + }, + 'skip': 'live', }] def _real_extract(self, url): @@ -137,6 +158,7 @@ class KickVODIE(KickBaseIE): 'categories': ('livestream', 'categories', ..., 'name', {str}), 'view_count': ('views', {int_or_none}), 'age_limit': ('livestream', 'is_mature', {bool}, {lambda x: 18 if x else 0}), + 'is_live': ('livestream', 'is_live', {bool}), }), } diff --git a/yt-dlp/yt_dlp/extractor/tver.py b/yt-dlp/yt_dlp/extractor/tver.py index a3dbabfd1e..ffcc6a76b7 100644 --- a/yt-dlp/yt_dlp/extractor/tver.py +++ b/yt-dlp/yt_dlp/extractor/tver.py @@ -45,6 +45,8 @@ class TVerIE(StreaksBaseIE): 'release_timestamp': 1651453200, 'release_date': '20220502', '_old_archive_ids': ['brightcovenew ref:baeebeac-a2a6-4dbf-9eb3-c40d59b40068'], + 'series_id': 'sru35hwdd2', + 'season_id': 'ss2lcn4af6', }, }, { # via Brightcove backend (deprecated) @@ -67,6 +69,8 @@ class TVerIE(StreaksBaseIE): 'upload_date': '20220501', 'release_timestamp': 1651453200, 'release_date': '20220502', + 'series_id': 'sru35hwdd2', + 'season_id': 'ss2lcn4af6', }, 'params': {'extractor_args': {'tver': {'backend': ['brightcove']}}}, }, { @@ -202,6 +206,8 @@ class TVerIE(StreaksBaseIE): 'description': ('description', {str}), 'release_timestamp': ('viewStatus', 'startAt', {int_or_none}), 'episode_number': ('no', {int_or_none}), + 'series_id': ('seriesID', {str}), + 'season_id': ('seasonID', {str}), }), } diff --git a/yt-dlp/yt_dlp/extractor/youtube/_base.py b/yt-dlp/yt_dlp/extractor/youtube/_base.py index 4ee54cc11e..5a0f55c53e 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/_base.py +++ b/yt-dlp/yt_dlp/extractor/youtube/_base.py @@ -310,6 +310,8 @@ INNERTUBE_CLIENTS = { }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 7, 'SUPPORTS_COOKIES': True, + # See: https://github.com/youtube/cobalt/blob/main/cobalt/browser/user_agent/user_agent_platform_info.cc#L506 + 'AUTHENTICATED_USER_AGENT': 'Mozilla/5.0 (ChromiumStylePlatform) Cobalt/25.lts.30.1034943-gold (unlike Gecko), Unknown_TV_Unknown_0/Unknown (Unknown, Unknown)', }, 'tv_simply': { 'INNERTUBE_CONTEXT': { @@ -368,6 +370,7 @@ def build_innertube_clients(): ytcfg.setdefault('REQUIRE_AUTH', False) ytcfg.setdefault('SUPPORTS_COOKIES', False) ytcfg.setdefault('PLAYER_PARAMS', None) + ytcfg.setdefault('AUTHENTICATED_USER_AGENT', None) ytcfg['INNERTUBE_CONTEXT']['client'].setdefault('hl', 'en') _, base_client, variant = _split_innertube_client(client) @@ -655,7 +658,14 @@ class YoutubeBaseInfoExtractor(InfoExtractor): _YT_INITIAL_PLAYER_RESPONSE_RE = r'ytInitialPlayerResponse\s*=' def _get_default_ytcfg(self, client='web'): - return copy.deepcopy(INNERTUBE_CLIENTS[client]) + ytcfg = copy.deepcopy(INNERTUBE_CLIENTS[client]) + + # Currently, only the tv client needs to use an alternative user-agent when logged-in + if ytcfg.get('AUTHENTICATED_USER_AGENT') and self.is_authenticated: + client_context = ytcfg.setdefault('INNERTUBE_CONTEXT', {}).setdefault('client', {}) + client_context['userAgent'] = ytcfg['AUTHENTICATED_USER_AGENT'] + + return ytcfg def _get_innertube_host(self, client='web'): return INNERTUBE_CLIENTS[client]['INNERTUBE_HOST'] @@ -954,7 +964,8 @@ class YoutubeBaseInfoExtractor(InfoExtractor): ytcfg = self.extract_ytcfg(video_id, webpage) or {} # Workaround for https://github.com/yt-dlp/yt-dlp/issues/12563 - if client == 'tv': + # But it's not effective when logged-in + if client == 'tv' and not self.is_authenticated: config_info = traverse_obj(ytcfg, ( 'INNERTUBE_CONTEXT', 'client', 'configInfo', {dict})) or {} config_info.pop('appInstallData', None) diff --git a/yt-dlp/yt_dlp/extractor/youtube/_video.py b/yt-dlp/yt_dlp/extractor/youtube/_video.py index 6a1476f759..afb1226cfa 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/_video.py +++ b/yt-dlp/yt_dlp/extractor/youtube/_video.py @@ -3611,6 +3611,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if f.get('source_preference') is None: f['source_preference'] = -1 + # Deprioritize since its pre-merged m3u8 formats may have lower quality audio streams + if client_name == 'web_safari' and proto == 'hls' and live_status != 'is_live': + f['source_preference'] -= 1 + if missing_pot: f['format_note'] = join_nonempty(f.get('format_note'), 'MISSING POT', delim=' ') f['source_preference'] -= 20 diff --git a/yt-dlp/yt_dlp/version.py b/yt-dlp/yt_dlp/version.py index fa2a637c06..cde18db454 100644 --- a/yt-dlp/yt_dlp/version.py +++ b/yt-dlp/yt_dlp/version.py @@ -1,8 +1,8 @@ # Autogenerated by devscripts/update-version.py -__version__ = '2025.08.22' +__version__ = '2025.08.27' -RELEASE_GIT_HEAD = '5c8bcfdbc638dfde13e93157637d8521413ed774' +RELEASE_GIT_HEAD = '8cd37b85d492edb56a4f7506ea05527b85a6b02b' VARIANT = None @@ -12,4 +12,4 @@ CHANNEL = 'stable' ORIGIN = 'yt-dlp/yt-dlp' -_pkg_version = '2025.08.22' +_pkg_version = '2025.08.27'