diff --git a/.github/update.log b/.github/update.log index 11f5e62b61..19e1face32 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1078,3 +1078,4 @@ Update On Wed Jul 30 20:44:03 CEST 2025 Update On Thu Jul 31 20:43:42 CEST 2025 Update On Fri Aug 1 20:44:14 CEST 2025 Update On Sat Aug 2 20:40:30 CEST 2025 +Update On Sun Aug 3 20:40:33 CEST 2025 diff --git a/bbdown/BBDown/Program.cs b/bbdown/BBDown/Program.cs index edb0157e11..47511598eb 100644 --- a/bbdown/BBDown/Program.cs +++ b/bbdown/BBDown/Program.cs @@ -839,7 +839,6 @@ partial class Program { return audioTracks .OrderBy(a => encodingPriority.GetValueOrDefault(a.shortCodecs, (byte)100)) - .ThenByDescending(a => Convert.ToInt32(a.id)) .ThenBy(a => audioAscending ? a.bandwith : -a.bandwith) .ToList(); } diff --git a/clash-meta-android/.github/workflows/build-debug.yaml b/clash-meta-android/.github/workflows/build-debug.yaml index b201cff1ff..e2cf8dde3c 100644 --- a/clash-meta-android/.github/workflows/build-debug.yaml +++ b/clash-meta-android/.github/workflows/build-debug.yaml @@ -40,6 +40,12 @@ jobs: restore-keys: | ${{ runner.os }}-go- + - name: Update CA + run: | + sudo apt-get update && sudo apt-get install ca-certificates + sudo update-ca-certificates + cp -f /etc/ssl/certs/ca-certificates.crt core/src/foss/golang/clash/component/ca/ca-certificates.crt + # - name: Signing properties # env: # SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }} diff --git a/clash-meta-android/.github/workflows/build-pre-release.yaml b/clash-meta-android/.github/workflows/build-pre-release.yaml index 36e4ff562a..70aadbbea6 100644 --- a/clash-meta-android/.github/workflows/build-pre-release.yaml +++ b/clash-meta-android/.github/workflows/build-pre-release.yaml @@ -38,6 +38,12 @@ jobs: restore-keys: | ${{ runner.os }}-go- + - name: Update CA + run: | + sudo apt-get update && sudo apt-get install ca-certificates + sudo update-ca-certificates + cp -f /etc/ssl/certs/ca-certificates.crt core/src/foss/golang/clash/component/ca/ca-certificates.crt + - name: Signing properties env: SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }} diff --git a/clash-meta-android/.github/workflows/build-release.yaml b/clash-meta-android/.github/workflows/build-release.yaml index 9ecfc50eba..9718c9a5ff 100644 --- a/clash-meta-android/.github/workflows/build-release.yaml +++ b/clash-meta-android/.github/workflows/build-release.yaml @@ -77,6 +77,12 @@ jobs: git push --follow-tags fi + - name: Update CA + run: | + sudo apt-get update && sudo apt-get install ca-certificates + sudo update-ca-certificates + cp -f /etc/ssl/certs/ca-certificates.crt core/src/foss/golang/clash/component/ca/ca-certificates.crt + - name: Signing properties env: SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }} diff --git a/clash-nyanpasu/backend/Cargo.lock b/clash-nyanpasu/backend/Cargo.lock index a620d7f20a..db2c62c8bf 100644 --- a/clash-nyanpasu/backend/Cargo.lock +++ b/clash-nyanpasu/backend/Cargo.lock @@ -6409,9 +6409,9 @@ dependencies = [ [[package]] name = "oxc_allocator" -version = "0.79.1" +version = "0.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387e852fa63147768141c5d1d4ca62adf5de4d0c8cfc502d60ebc863dd60fef7" +checksum = "0b1301e61c53d6f0a252a0e9f6495b112f403e6fcee7f7040055a77682e68768" dependencies = [ "allocator-api2", "bumpalo", @@ -6422,9 +6422,9 @@ dependencies = [ [[package]] name = "oxc_ast" -version = "0.79.1" +version = "0.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2865881163de2608e5e9981746de727930e5c23c92954973903adaa22601aee" +checksum = "670cf83ede2349153ffcd30105aa42aa34de493919a2dea628cfbfada8461b97" dependencies = [ "bitflags 2.9.1", "oxc_allocator", @@ -6438,9 +6438,9 @@ dependencies = [ [[package]] name = "oxc_ast_macros" -version = "0.79.1" +version = "0.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9cff8428e0513c6f2b2fd55cd1217f8243ba806ba1914d9783a4c47cf85eb1" +checksum = "79a8d5845a8fce343729abff7c46bfe839a6a763c2ec805dcfb85c83fd15a8b5" dependencies = [ "phf 0.12.1", "proc-macro2", @@ -6450,9 +6450,9 @@ dependencies = [ [[package]] name = "oxc_ast_visit" -version = "0.79.1" +version = "0.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97271b9fd4b8bceb887feaa73f4f8b5c1d4e2348124ab569be8982c24f1b95da" +checksum = "bd499527d053a9932b0263fdb93dd5f8b2b319e039b16dcefe35c41d12a6aa2d" dependencies = [ "oxc_allocator", "oxc_ast", @@ -6462,18 +6462,18 @@ dependencies = [ [[package]] name = "oxc_data_structures" -version = "0.79.1" +version = "0.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1442a0ccd0667148c49e5fcb79d0578aaef863f65d9f30b4dbce262c7ccac8" +checksum = "15f63e3fbabe99f18ad51db5d68d5dc5c407d038f67e4faf3ca9a4a3e6836122" dependencies = [ "rustversion", ] [[package]] name = "oxc_diagnostics" -version = "0.79.1" +version = "0.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af2ddf131f04efd708f8d4325183ebd4b73ce70cfe75ae0f9718985193faad74" +checksum = "395715864b505c0f1ac61fd5fd9dad03b85529323be5c69143c28d10c10a87e3" dependencies = [ "cow-utils", "oxc-miette", @@ -6482,12 +6482,14 @@ dependencies = [ [[package]] name = "oxc_ecmascript" -version = "0.79.1" +version = "0.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea921bd0bf9d2827de585f6e2e5177b763a7b3de24016a493c69aae022750bfe" +checksum = "420a96025457985ecb7bc537d80a1760d0326b4faa627f3362f1203cc802993e" dependencies = [ + "cow-utils", "num-bigint", "num-traits", + "oxc_allocator", "oxc_ast", "oxc_span", "oxc_syntax", @@ -6495,9 +6497,9 @@ dependencies = [ [[package]] name = "oxc_estree" -version = "0.79.1" +version = "0.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4adb8dff81cbddb3c2b722c6bf839bb222f57d2d6d7bda27326cdeb642d76c3a" +checksum = "a75a6aca8a627435939ab1b216b3013fcd6797915faa220566978179b11315b2" [[package]] name = "oxc_index" @@ -6507,9 +6509,9 @@ checksum = "2fa07b0cfa997730afed43705766ef27792873fdf5215b1391949fec678d2392" [[package]] name = "oxc_parser" -version = "0.79.1" +version = "0.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3994791fdb377e2dd4a27a4ce2ba8b4350bdf155bb1389c0b241ae4982f61855" +checksum = "e3795e25917b06c517761baa5fea9fe2ce1f67180608945375d67f605b4f7980" dependencies = [ "bitflags 2.9.1", "cow-utils", @@ -6530,9 +6532,9 @@ dependencies = [ [[package]] name = "oxc_regular_expression" -version = "0.79.1" +version = "0.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ae00a7b2d35c832f97da58d5faf852c4e8e70ab271550109c1d77e1b1b43b" +checksum = "d5649b52cca897621f4f403c6d92bddb45fade8da0f01e68e3b88240d232fda7" dependencies = [ "bitflags 2.9.1", "oxc_allocator", @@ -6546,9 +6548,9 @@ dependencies = [ [[package]] name = "oxc_span" -version = "0.79.1" +version = "0.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c27ab8f00b330fdbc3fc22872851f48d79284dda51428043bb2ed4e0d34816" +checksum = "96fd662d7483121c8acee269f0ec52759fc506e7c92d52453f716d4ab4b25f2b" dependencies = [ "compact_str", "oxc-miette", @@ -6559,9 +6561,9 @@ dependencies = [ [[package]] name = "oxc_syntax" -version = "0.79.1" +version = "0.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a89b351b05d9a44ea8291fb48a6d32f4687ebc7e12b95d1695b46e12f96e2b" +checksum = "74e67afd539df6cb1f8de52e3df056b745c4f91b5e5ce59c3b2c398bd5ec54b8" dependencies = [ "bitflags 2.9.1", "cow-utils", @@ -7501,9 +7503,9 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redb" -version = "2.6.1" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef838cd981b5c46e9e91e20e4623e43b29b5c251eb245b34da0cbd2da09ab27" +checksum = "59b38b05028f398f08bea4691640503ec25fcb60b82fb61ce1f8fd1f4fccd3f7" dependencies = [ "libc", ] @@ -9879,9 +9881,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", diff --git a/clash-nyanpasu/backend/tauri/Cargo.toml b/clash-nyanpasu/backend/tauri/Cargo.toml index 4215e80084..b723343524 100644 --- a/clash-nyanpasu/backend/tauri/Cargo.toml +++ b/clash-nyanpasu/backend/tauri/Cargo.toml @@ -172,12 +172,12 @@ display-info = "0.5.0" # should be removed after upgrading to tauri v2 # OXC (The Oxidation Compiler) # We use it to parse and transpile the old script profile to esm based script profile -oxc_parser = "0.79" -oxc_allocator = "0.79" -oxc_span = "0.79" -oxc_ast = "0.79" -oxc_syntax = "0.79" -oxc_ast_visit = "0.79" +oxc_parser = "0.80" +oxc_allocator = "0.80" +oxc_span = "0.80" +oxc_ast = "0.80" +oxc_syntax = "0.80" +oxc_ast_visit = "0.80" # Lua Integration mlua = { version = "0.11", features = [ diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index 5f3b1d41e2..696c56f56f 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -80,7 +80,7 @@ "clsx": "2.1.1", "core-js": "3.44.0", "filesize": "11.0.2", - "meta-json-schema": "1.19.11", + "meta-json-schema": "1.19.12", "monaco-yaml": "5.4.0", "nanoid": "5.1.5", "sass-embedded": "1.89.2", diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index 25fefee73b..2dfdc9bfc3 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -415,8 +415,8 @@ importers: specifier: 11.0.2 version: 11.0.2 meta-json-schema: - specifier: 1.19.11 - version: 1.19.11 + specifier: 1.19.12 + version: 1.19.12 monaco-yaml: specifier: 5.4.0 version: 5.4.0(monaco-editor@0.52.2) @@ -6343,8 +6343,8 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - meta-json-schema@1.19.11: - resolution: {integrity: sha512-qIcE1GmsF2b2CNMl7/C1w9nLaGOI1tP8/DrAZm0bJ+tkUAoKL1G2ZsdRUUiKFAy2Z0mbmQ8GCQ/+IBhjMRxCmQ==} + meta-json-schema@1.19.12: + resolution: {integrity: sha512-aiI0eILS8y474h5Wmmxp7GgxaFimqEc3z3L4EfZMjqec7N7q7wbXVrZHIR5mUUDHpHagZYFoT1Qf1QNrHMukcw==} engines: {node: '>=18', pnpm: '>=9'} micromark-core-commonmark@2.0.1: @@ -14976,7 +14976,7 @@ snapshots: merge2@1.4.1: {} - meta-json-schema@1.19.11: {} + meta-json-schema@1.19.12: {} micromark-core-commonmark@2.0.1: dependencies: diff --git a/lede/package/firmware/linux-firmware/misc.mk b/lede/package/firmware/linux-firmware/misc.mk index d0956f77b3..ad684a2bc5 100644 --- a/lede/package/firmware/linux-firmware/misc.mk +++ b/lede/package/firmware/linux-firmware/misc.mk @@ -1,3 +1,13 @@ +Package/eip197-firmware = $(call Package/firmware-default,Inside Secure EIP197 firmware) +define Package/eip197-firmware/install + $(INSTALL_DIR) $(1)/lib/firmware + $(INSTALL_DATA) \ + $(PKG_BUILD_DIR)/inside-secure/eip197_minifw/ifpp.bin \ + $(PKG_BUILD_DIR)/inside-secure/eip197_minifw/ipue.bin \ + $(1)/lib/firmware +endef +$(eval $(call BuildPackage,eip197-firmware)) + Package/eip197-mini-firmware = $(call Package/firmware-default,Inside Secure EIP197 mini firmware) define Package/eip197-mini-firmware/install $(INSTALL_DIR) $(1)/lib/firmware/inside-secure/eip197_minifw diff --git a/lede/package/qca/nss-eip-firmware/Makefile b/lede/package/qca/nss-eip-firmware/Makefile deleted file mode 100644 index de9558e0ca..0000000000 --- a/lede/package/qca/nss-eip-firmware/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -include $(TOPDIR)/rules.mk - -PKG_NAME:=nss-eip-firmware -PKG_VERSION=2.5.7 -PKG_RELEASE:=1 - -PKG_MAINTAINER:=Robert Marko - -include $(INCLUDE_DIR)/package.mk - -define Package/nss-eip-firmware - SECTION:=firmware - CATEGORY:=Firmware - TITLE:=NSS EIP-197 firmware - DEPENDS:=@(TARGET_qualcommax_ipq807x||TARGET_qualcommax_ipq60xx) -endef - -define Build/Compile - -endef - -define Package/nss-eip-firmware/install - $(INSTALL_DIR) $(1)/lib/firmware/ - $(INSTALL_DATA) \ - $(PKG_BUILD_DIR)/ifpp.bin $(1)/lib/firmware/ifpp.bin - $(INSTALL_DATA) \ - $(PKG_BUILD_DIR)/ipue.bin $(1)/lib/firmware/ipue.bin - $(INSTALL_DATA) \ - $(PKG_BUILD_DIR)/ofpp.bin $(1)/lib/firmware/ofpp.bin - $(INSTALL_DATA) \ - $(PKG_BUILD_DIR)/opue.bin $(1)/lib/firmware/opue.bin -endef - -$(eval $(call BuildPackage,nss-eip-firmware)) diff --git a/lede/package/qca/nss-eip-firmware/src/ifpp.bin b/lede/package/qca/nss-eip-firmware/src/ifpp.bin deleted file mode 100644 index 033ad55526..0000000000 Binary files a/lede/package/qca/nss-eip-firmware/src/ifpp.bin and /dev/null differ diff --git a/lede/package/qca/nss-eip-firmware/src/ipue.bin b/lede/package/qca/nss-eip-firmware/src/ipue.bin deleted file mode 100644 index 153a7b4e8a..0000000000 Binary files a/lede/package/qca/nss-eip-firmware/src/ipue.bin and /dev/null differ diff --git a/lede/package/qca/nss-eip-firmware/src/ofpp.bin b/lede/package/qca/nss-eip-firmware/src/ofpp.bin deleted file mode 100644 index 3d07459137..0000000000 Binary files a/lede/package/qca/nss-eip-firmware/src/ofpp.bin and /dev/null differ diff --git a/lede/package/qca/nss-eip-firmware/src/opue.bin b/lede/package/qca/nss-eip-firmware/src/opue.bin deleted file mode 100644 index 80fb1a211d..0000000000 Binary files a/lede/package/qca/nss-eip-firmware/src/opue.bin and /dev/null differ diff --git a/lede/package/qca/qca-nss-crypto/Makefile b/lede/package/qca/qca-nss-crypto/Makefile index 13844ee95e..6f70464f36 100644 --- a/lede/package/qca/qca-nss-crypto/Makefile +++ b/lede/package/qca/qca-nss-crypto/Makefile @@ -16,17 +16,17 @@ include $(INCLUDE_DIR)/package.mk # v1.0 is for Akronite # v2.0 is for Hawkeye/Cypress/Maple -ifneq (, $(findstring $(CONFIG_TARGET_SUBTARGET), "ipq807x" "ipq60xx")) -NSS_CRYPTO_DIR:=v2.0 -else +ifeq ($(BOARD),ipq806x) NSS_CRYPTO_DIR:=v1.0 +else +NSS_CRYPTO_DIR:=v2.0 endif define KernelPackage/qca-nss-crypto SECTION:=kernel CATEGORY:=Kernel modules SUBMENU:=Cryptographic API modules - DEPENDS:=@TARGET_qualcommax +kmod-qca-nss-drv +TARGET_qualcommax_ipq807x:nss-eip-firmware +TARGET_qualcommax_ipq60xx:nss-eip-firmware + DEPENDS:=@TARGET_qualcommax +kmod-qca-nss-drv +eip197-firmware TITLE:=Kernel driver for NSS crypto driver FILES:=$(PKG_BUILD_DIR)/$(NSS_CRYPTO_DIR)/src/qca-nss-crypto.ko \ $(PKG_BUILD_DIR)/$(NSS_CRYPTO_DIR)/tool/qca-nss-crypto-tool.ko diff --git a/lede/package/qca/qca-nss-ecm/Makefile b/lede/package/qca/qca-nss-ecm/Makefile index c71f60a999..71ab3c8854 100644 --- a/lede/package/qca/qca-nss-ecm/Makefile +++ b/lede/package/qca/qca-nss-ecm/Makefile @@ -28,7 +28,7 @@ define KernelPackage/qca-nss-ecm +@NSS_DRV_VIRT_IF_ENABLE \ +@NSS_DRV_WIFI_ENABLE \ +kmod-qca-nss-drv \ - +kmod-bonding +kmod-nf-conntrack \ + +kmod-bonding +kmod-nf-conntrack \ +kmod-ppp +kmod-pppoe +kmod-pptp \ +PACKAGE_iptables:iptables-mod-physdev \ +PACKAGE_kmod-pppol2tp:kmod-pppol2tp \ @@ -65,15 +65,15 @@ EXTRA_CFLAGS+= \ -I$(STAGING_DIR)/usr/include/nat46 ifeq ($(BOARD),qualcommax) -ECM_MAKE_OPTS+=ECM_FRONT_END_NSS_ENABLE=y \ - ECM_FRONT_END_SFE_ENABLE=n \ - ECM_NON_PORTED_SUPPORT_ENABLE=y \ - ECM_INTERFACE_BOND_ENABLE=y \ - ECM_INTERFACE_VLAN_ENABLE=y \ - ECM_CLASSIFIER_MARK_ENABLE=y \ - ECM_CLASSIFIER_DSCP_ENABLE=y \ - ECM_CLASSIFIER_PCC_ENABLE=n \ - ECM_BAND_STEERING_ENABLE=n +ECM_MAKE_OPTS+= ECM_FRONT_END_NSS_ENABLE=y \ + ECM_FRONT_END_SFE_ENABLE=n \ + ECM_NON_PORTED_SUPPORT_ENABLE=y \ + ECM_INTERFACE_BOND_ENABLE=y \ + ECM_INTERFACE_VLAN_ENABLE=y \ + ECM_CLASSIFIER_MARK_ENABLE=y \ + ECM_CLASSIFIER_DSCP_ENABLE=y \ + ECM_CLASSIFIER_PCC_ENABLE=n \ + ECM_BAND_STEERING_ENABLE=n endif # Disable ECM IPv6 support when global IPv6 support is disabled. diff --git a/lede/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq6018-nss.dtsi b/lede/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq6018-nss.dtsi index 2d04ac1402..d05ff84b53 100644 --- a/lede/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq6018-nss.dtsi +++ b/lede/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq6018-nss.dtsi @@ -22,42 +22,42 @@ nss0: nss@40000000 { compatible = "qcom,nss"; interrupts = <0 402 0x1>, <0 401 0x1>, <0 400 0x1>, - <0 399 0x1>, <0 398 0x1>, <0 397 0x1>, - <0 396 0x1>, <0 395 0x1>, <0 394 0x1>, - <0 393 0x1>; + <0 399 0x1>, <0 398 0x1>, <0 397 0x1>, + <0 396 0x1>, <0 395 0x1>, <0 394 0x1>, + <0 393 0x1>; reg = <0x0 0x39000000 0x0 0x1000>, <0x0 0x0b111000 0x0 0x1000>; reg-names = "nphys", "qgic-phys"; clocks = <&gcc GCC_NSS_NOC_CLK>, - <&gcc GCC_NSS_PTP_REF_CLK>, - <&gcc GCC_NSS_CSR_CLK>, <&gcc GCC_NSS_CFG_CLK>, - <&gcc GCC_NSSNOC_QOSGEN_REF_CLK>, - <&gcc GCC_NSSNOC_SNOC_CLK>, - <&gcc GCC_NSSNOC_TIMEOUT_REF_CLK>, - <&gcc GCC_MEM_NOC_UBI32_CLK>, - <&gcc GCC_NSS_CE_AXI_CLK>, - <&gcc GCC_NSS_CE_APB_CLK>, - <&gcc GCC_NSSNOC_CE_AXI_CLK>, - <&gcc GCC_NSSNOC_CE_APB_CLK>, - <&gcc GCC_NSSNOC_UBI0_AHB_CLK>, - <&gcc GCC_UBI0_CORE_CLK>, - <&gcc GCC_UBI0_AHB_CLK>, - <&gcc GCC_UBI0_AXI_CLK>, - <&gcc GCC_UBI0_NC_AXI_CLK>, - <&gcc GCC_UBI0_UTCM_CLK>, - <&gcc GCC_SNOC_NSSNOC_CLK>; + <&gcc GCC_NSS_PTP_REF_CLK>, + <&gcc GCC_NSS_CSR_CLK>, <&gcc GCC_NSS_CFG_CLK>, + <&gcc GCC_NSSNOC_QOSGEN_REF_CLK>, + <&gcc GCC_NSSNOC_SNOC_CLK>, + <&gcc GCC_NSSNOC_TIMEOUT_REF_CLK>, + <&gcc GCC_MEM_NOC_UBI32_CLK>, + <&gcc GCC_NSS_CE_AXI_CLK>, + <&gcc GCC_NSS_CE_APB_CLK>, + <&gcc GCC_NSSNOC_CE_AXI_CLK>, + <&gcc GCC_NSSNOC_CE_APB_CLK>, + <&gcc GCC_NSSNOC_UBI0_AHB_CLK>, + <&gcc GCC_UBI0_CORE_CLK>, + <&gcc GCC_UBI0_AHB_CLK>, + <&gcc GCC_UBI0_AXI_CLK>, + <&gcc GCC_UBI0_NC_AXI_CLK>, + <&gcc GCC_UBI0_UTCM_CLK>, + <&gcc GCC_SNOC_NSSNOC_CLK>; clock-names = "nss-noc-clk", "nss-ptp-ref-clk", - "nss-csr-clk", "nss-cfg-clk", - "nss-nssnoc-qosgen-ref-clk", - "nss-nssnoc-snoc-clk", - "nss-nssnoc-timeout-ref-clk", - "nss-mem-noc-ubi32-clk", - "nss-ce-axi-clk", "nss-ce-apb-clk", - "nss-nssnoc-ce-axi-clk", - "nss-nssnoc-ce-apb-clk", - "nss-nssnoc-ahb-clk", - "nss-core-clk", "nss-ahb-clk", - "nss-axi-clk", "nss-nc-axi-clk", - "nss-utcm-clk", "nss-snoc-nssnoc-clk"; + "nss-csr-clk", "nss-cfg-clk", + "nss-nssnoc-qosgen-ref-clk", + "nss-nssnoc-snoc-clk", + "nss-nssnoc-timeout-ref-clk", + "nss-mem-noc-ubi32-clk", + "nss-ce-axi-clk", "nss-ce-apb-clk", + "nss-nssnoc-ce-axi-clk", + "nss-nssnoc-ce-apb-clk", + "nss-nssnoc-ahb-clk", + "nss-core-clk", "nss-ahb-clk", + "nss-axi-clk", "nss-nc-axi-clk", + "nss-utcm-clk", "nss-snoc-nssnoc-clk"; qcom,id = <0>; qcom,num-queue = <4>; qcom,num-irq = <10>; @@ -184,8 +184,6 @@ reg_offset = <0x80000>; qcom,ifpp-enabled; qcom,ipue-enabled; - qcom,ofpp-enabled; - qcom,opue-enabled; }; }; }; diff --git a/lede/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq8071-ap8220.dts b/lede/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq8071-ap8220.dts index be7798b591..79d1fe8161 100644 --- a/lede/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq8071-ap8220.dts +++ b/lede/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq8071-ap8220.dts @@ -227,11 +227,26 @@ &qpic_nand { status = "okay"; + partitions { + status = "disabled"; + }; + nand@0 { reg = <0>; nand-ecc-strength = <4>; nand-ecc-step-size = <512>; nand-bus-width = <8>; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + partition@0 { + label = "rootfs"; + reg = <0x0 0x0>; + }; + }; }; }; diff --git a/lede/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq8074-nss.dtsi b/lede/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq8074-nss.dtsi index d85a37230a..0d7e94c02e 100644 --- a/lede/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq8074-nss.dtsi +++ b/lede/target/linux/qualcommax/files/arch/arm64/boot/dts/qcom/ipq8074-nss.dtsi @@ -22,57 +22,57 @@ nss0: nss@40000000 { compatible = "qcom,nss"; interrupts = , - , - , - , - , - , - , - , - , - ; + , + , + , + , + , + , + , + , + ; reg = <0x39000000 0x1000>, <0x38000000 0x30000>, <0x0b111000 0x1000>; reg-names = "nphys", "vphys", "qgic-phys"; clocks = <&gcc GCC_NSS_NOC_CLK>, - <&gcc GCC_NSS_PTP_REF_CLK>, - <&gcc GCC_NSS_CSR_CLK>, - <&gcc GCC_NSS_CFG_CLK>, - <&gcc GCC_NSS_IMEM_CLK>, - <&gcc GCC_NSSNOC_QOSGEN_REF_CLK>, - <&gcc GCC_MEM_NOC_NSS_AXI_CLK>, - <&gcc GCC_NSSNOC_SNOC_CLK>, - <&gcc GCC_NSSNOC_TIMEOUT_REF_CLK>, - <&gcc GCC_NSS_CE_AXI_CLK>, - <&gcc GCC_NSS_CE_APB_CLK>, - <&gcc GCC_NSSNOC_CE_AXI_CLK>, - <&gcc GCC_NSSNOC_CE_APB_CLK>, - <&gcc GCC_NSSNOC_UBI0_AHB_CLK>, - <&gcc GCC_UBI0_CORE_CLK>, - <&gcc GCC_UBI0_AHB_CLK>, - <&gcc GCC_UBI0_AXI_CLK>, - <&gcc GCC_UBI0_MPT_CLK>, - <&gcc GCC_UBI0_NC_AXI_CLK>; - clock-names = "nss-noc-clk", - "nss-ptp-ref-clk", - "nss-csr-clk", - "nss-cfg-clk", - "nss-imem-clk", - "nss-nssnoc-qosgen-ref-clk", - "nss-mem-noc-nss-axi-clk", - "nss-nssnoc-snoc-clk", - "nss-nssnoc-timeout-ref-clk", - "nss-ce-axi-clk", - "nss-ce-apb-clk", - "nss-nssnoc-ce-axi-clk", - "nss-nssnoc-ce-apb-clk", - "nss-nssnoc-ahb-clk", - "nss-core-clk", - "nss-ahb-clk", - "nss-axi-clk", - "nss-mpt-clk", - "nss-nc-axi-clk"; + <&gcc GCC_NSS_PTP_REF_CLK>, + <&gcc GCC_NSS_CSR_CLK>, + <&gcc GCC_NSS_CFG_CLK>, + <&gcc GCC_NSS_IMEM_CLK>, + <&gcc GCC_NSSNOC_QOSGEN_REF_CLK>, + <&gcc GCC_MEM_NOC_NSS_AXI_CLK>, + <&gcc GCC_NSSNOC_SNOC_CLK>, + <&gcc GCC_NSSNOC_TIMEOUT_REF_CLK>, + <&gcc GCC_NSS_CE_AXI_CLK>, + <&gcc GCC_NSS_CE_APB_CLK>, + <&gcc GCC_NSSNOC_CE_AXI_CLK>, + <&gcc GCC_NSSNOC_CE_APB_CLK>, + <&gcc GCC_NSSNOC_UBI0_AHB_CLK>, + <&gcc GCC_UBI0_CORE_CLK>, + <&gcc GCC_UBI0_AHB_CLK>, + <&gcc GCC_UBI0_AXI_CLK>, + <&gcc GCC_UBI0_MPT_CLK>, + <&gcc GCC_UBI0_NC_AXI_CLK>; + clock-names = "nss-noc-clk", + "nss-ptp-ref-clk", + "nss-csr-clk", + "nss-cfg-clk", + "nss-imem-clk", + "nss-nssnoc-qosgen-ref-clk", + "nss-mem-noc-nss-axi-clk", + "nss-nssnoc-snoc-clk", + "nss-nssnoc-timeout-ref-clk", + "nss-ce-axi-clk", + "nss-ce-apb-clk", + "nss-nssnoc-ce-axi-clk", + "nss-nssnoc-ce-apb-clk", + "nss-nssnoc-ahb-clk", + "nss-core-clk", + "nss-ahb-clk", + "nss-axi-clk", + "nss-mpt-clk", + "nss-nc-axi-clk"; qcom,id = <0>; qcom,num-queue = <4>; qcom,num-irq = <10>; @@ -113,56 +113,56 @@ nss1: nss@40800000 { compatible = "qcom,nss"; interrupts = , - , - , - , - , - , - , - , - ; + , + , + , + , + , + , + , + ; reg = <0x39400000 0x1000>, <0x38030000 0x30000>, <0x0b111000 0x1000>; reg-names = "nphys", "vphys", "qgic-phys"; clocks = <&gcc GCC_NSS_NOC_CLK>, - <&gcc GCC_NSS_PTP_REF_CLK>, - <&gcc GCC_NSS_CSR_CLK>, - <&gcc GCC_NSS_CFG_CLK>, - <&gcc GCC_NSS_IMEM_CLK>, - <&gcc GCC_NSSNOC_QOSGEN_REF_CLK>, - <&gcc GCC_MEM_NOC_NSS_AXI_CLK>, - <&gcc GCC_NSSNOC_SNOC_CLK>, - <&gcc GCC_NSSNOC_TIMEOUT_REF_CLK>, - <&gcc GCC_NSS_CE_AXI_CLK>, - <&gcc GCC_NSS_CE_APB_CLK>, - <&gcc GCC_NSSNOC_CE_AXI_CLK>, - <&gcc GCC_NSSNOC_CE_APB_CLK>, - <&gcc GCC_NSSNOC_UBI1_AHB_CLK>, - <&gcc GCC_UBI1_CORE_CLK>, - <&gcc GCC_UBI1_AHB_CLK>, - <&gcc GCC_UBI1_AXI_CLK>, - <&gcc GCC_UBI1_MPT_CLK>, - <&gcc GCC_UBI1_NC_AXI_CLK>; - clock-names = "nss-noc-clk", - "nss-ptp-ref-clk", - "nss-csr-clk", - "nss-cfg-clk", - "nss-imem-clk", - "nss-nssnoc-qosgen-ref-clk", - "nss-mem-noc-nss-axi-clk", - "nss-nssnoc-snoc-clk", - "nss-nssnoc-timeout-ref-clk", - "nss-ce-axi-clk", - "nss-ce-apb-clk", - "nss-nssnoc-ce-axi-clk", - "nss-nssnoc-ce-apb-clk", - "nss-nssnoc-ahb-clk", - "nss-core-clk", - "nss-ahb-clk", - "nss-axi-clk", - "nss-mpt-clk", - "nss-nc-axi-clk"; + <&gcc GCC_NSS_PTP_REF_CLK>, + <&gcc GCC_NSS_CSR_CLK>, + <&gcc GCC_NSS_CFG_CLK>, + <&gcc GCC_NSS_IMEM_CLK>, + <&gcc GCC_NSSNOC_QOSGEN_REF_CLK>, + <&gcc GCC_MEM_NOC_NSS_AXI_CLK>, + <&gcc GCC_NSSNOC_SNOC_CLK>, + <&gcc GCC_NSSNOC_TIMEOUT_REF_CLK>, + <&gcc GCC_NSS_CE_AXI_CLK>, + <&gcc GCC_NSS_CE_APB_CLK>, + <&gcc GCC_NSSNOC_CE_AXI_CLK>, + <&gcc GCC_NSSNOC_CE_APB_CLK>, + <&gcc GCC_NSSNOC_UBI1_AHB_CLK>, + <&gcc GCC_UBI1_CORE_CLK>, + <&gcc GCC_UBI1_AHB_CLK>, + <&gcc GCC_UBI1_AXI_CLK>, + <&gcc GCC_UBI1_MPT_CLK>, + <&gcc GCC_UBI1_NC_AXI_CLK>; + clock-names = "nss-noc-clk", + "nss-ptp-ref-clk", + "nss-csr-clk", + "nss-cfg-clk", + "nss-imem-clk", + "nss-nssnoc-qosgen-ref-clk", + "nss-mem-noc-nss-axi-clk", + "nss-nssnoc-snoc-clk", + "nss-nssnoc-timeout-ref-clk", + "nss-ce-axi-clk", + "nss-ce-apb-clk", + "nss-nssnoc-ce-axi-clk", + "nss-nssnoc-ce-apb-clk", + "nss-nssnoc-ahb-clk", + "nss-core-clk", + "nss-ahb-clk", + "nss-axi-clk", + "nss-mpt-clk", + "nss-nc-axi-clk"; qcom,id = <1>; qcom,num-queue = <4>; qcom,num-irq = <9>; @@ -181,10 +181,10 @@ nss_crypto: qcom,nss_crypto { compatible = "qcom,nss-crypto"; - #address-cells = <1>; - #size-cells = <1>; qcom,max-contexts = <64>; qcom,max-context-size = <32>; + #address-cells = <1>; + #size-cells = <1>; ranges; eip197_node { @@ -192,11 +192,11 @@ reg-names = "crypto_pbase"; reg = <0x39800000 0x7ffff>; clocks = <&gcc GCC_NSS_CRYPTO_CLK>, - <&gcc GCC_NSSNOC_CRYPTO_CLK>, - <&gcc GCC_CRYPTO_PPE_CLK>; + <&gcc GCC_NSSNOC_CRYPTO_CLK>, + <&gcc GCC_CRYPTO_PPE_CLK>; clock-names = "crypto_clk", - "crypto_nocclk", - "crypto_ppeclk"; + "crypto_nocclk", + "crypto_ppeclk"; clock-frequency = /bits/ 64 <600000000 600000000 300000000>; qcom,dma-mask = <0xff>; qcom,transform-enabled; @@ -263,8 +263,6 @@ reg_offset = <0x80000>; qcom,ifpp-enabled; qcom,ipue-enabled; - qcom,ofpp-enabled; - qcom,opue-enabled; }; }; }; diff --git a/openwrt-packages/luci-app-amlogic/Makefile b/openwrt-packages/luci-app-amlogic/Makefile index 481f330293..21109ad94d 100644 --- a/openwrt-packages/luci-app-amlogic/Makefile +++ b/openwrt-packages/luci-app-amlogic/Makefile @@ -16,7 +16,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-amlogic -PKG_VERSION:=3.1.263 +PKG_VERSION:=3.1.265 PKG_RELEASE:=1 PKG_LICENSE:=GPL-2.0 License diff --git a/openwrt-packages/luci-app-amlogic/root/usr/share/amlogic/amlogic_check_firmware.sh b/openwrt-packages/luci-app-amlogic/root/usr/share/amlogic/amlogic_check_firmware.sh index 0edc3fec29..ba53749916 100755 --- a/openwrt-packages/luci-app-amlogic/root/usr/share/amlogic/amlogic_check_firmware.sh +++ b/openwrt-packages/luci-app-amlogic/root/usr/share/amlogic/amlogic_check_firmware.sh @@ -188,39 +188,21 @@ check_updated() { https://github.com/${server_firmware_url}/releases/expanded_assets/${firmware_releases_tag} )" - # Define regular expressions to match firmware download links and release dates - regex_href='href="([^"]+)"' - regex_datetime='datetime="([^"]+)"' - link_date_results=() - while [[ "${html_code}" =~ ${regex_href} ]]; do - href="${BASH_REMATCH[1]##*/}" - html_code="${html_code#*"${BASH_REMATCH[1]}"}" - if [[ "${html_code}" =~ $regex_datetime ]]; then - datetime="${BASH_REMATCH[1]}" + # Set the regular expression for the OpenWrt filename + op_file_pattern=".*_${BOARD}_.*k${main_line_version}\..*${firmware_suffix}" + # Find the
  • list item where the OpenWrt file is located + li_block=$(awk -v pattern="${op_file_pattern}" -v RS='
  • ' '$0 ~ pattern { print $0 ""; exit }' <<<"${html_code}") + [[ -z "${li_block}" ]] && tolog "02.03.01 No matching download links found." "1" - # Search for firmware download links that meet the criteria - if [[ "${href}" =~ .*_${BOARD}_.*${main_line_version}\.[0-9]+.*${firmware_suffix} ]] && - [[ ! "${href}" =~ \.sha ]]; then - link_date_results+=("${href}@${datetime}") - fi - - fi - done - - if [[ "${#link_date_results[*]}" -eq "0" ]]; then - tolog "02.01 No matching download links found." "1" - fi - - # Get the latest version - latest_url_date="$(echo "${link_date_results[*]}" | tr ' ' '\n' | sort -urV | head -n 1)" - if [[ -n "${latest_url_date}" ]]; then - latest_url="$(echo "${latest_url_date}" | awk -F '@' '{print $1}')" - latest_updated_at="$(echo "${latest_url_date}" | awk -F '@' '{print $2}')" - # Convert to readable date format - date_display_format="$(echo ${latest_updated_at} | tr 'T' '(' | tr 'Z' ')')" - else - tolog "02.02 The search results for releases are empty." "1" - fi + # Find the OpenWrt filename + latest_url=$(echo "${li_block}" | grep -o "/[^\"]*_${BOARD}_.*k${main_line_version}\.[^\"']*${firmware_suffix}" | sort -urV | head -n 1 | xargs basename 2>/dev/null) + tolog "02.03.02 OpenWrt file: ${latest_url}" + # Find the date of the latest update + latest_updated_at=$(echo "${li_block}" | grep -o 'datetime="[^"]*"' | sed 's/datetime="//; s/"//') + tolog "02.03.03 Latest updated at: ${latest_updated_at}" + # Format the date for display + date_display_format="$(echo ${latest_updated_at} | tr 'T' '(' | tr 'Z' ')')" + [[ -z "${latest_url}" || -z "${latest_updated_at}" ]] && tolog "02.03.04 The download URL or date is invalid." "1" # Check the firmware update code down_check_code="${latest_updated_at}.${main_line_version}" @@ -228,7 +210,7 @@ check_updated() { if [[ -s "${op_release_code}" ]]; then update_check_code="$(cat ${op_release_code} | xargs)" if [[ -n "${update_check_code}" && "${update_check_code}" == "${down_check_code}" ]]; then - tolog "02.02 Already the latest version, no need to update." "1" + tolog "02.03.05 Already the latest version, no need to update." "1" fi fi @@ -236,7 +218,7 @@ check_updated() { if [[ -n "${latest_url}" ]]; then tolog ' Latest updated: '${date_display_format}'' "1" else - tolog "02.03 No OpenWrt available, please use another kernel branch." "1" + tolog "02.04 No OpenWrt available, please use another kernel branch." "1" fi exit 0 diff --git a/openwrt-passwall/README.md b/openwrt-passwall/README.md index 81569a71bd..3b63358719 100644 --- a/openwrt-passwall/README.md +++ b/openwrt-passwall/README.md @@ -3,6 +3,17 @@ ## 📌如何能编译到最新代码? +### 方法1: + +执行 `./scripts/feeds update -a` 操作前,在 `feeds.conf.default` **顶部**插入如下代码: + +``` +src-git passwall_packages https://github.com/xiaorouji/openwrt-passwall-packages.git;main +src-git passwall_luci https://github.com/xiaorouji/openwrt-passwall.git;main +``` + +### 方法2: + 在 `./scripts/feeds install -a` 操作完成后,执行以下命令: ```shell @@ -13,8 +24,4 @@ git clone https://github.com/xiaorouji/openwrt-passwall-packages package/passwal # 移除 openwrt feeds 过时的luci版本 rm -rf feeds/luci/applications/luci-app-passwall git clone https://github.com/xiaorouji/openwrt-passwall package/passwall-luci - -# 更新 golang 1.25 版本 -rm -rf feeds/packages/lang/golang -git clone https://github.com/sbwml/packages_lang_golang -b 25.x feeds/packages/lang/golang ``` diff --git a/openwrt-passwall/luci-app-passwall/luasrc/controller/passwall.lua b/openwrt-passwall/luci-app-passwall/luasrc/controller/passwall.lua index f4c6393a88..2d62b8dcbb 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/controller/passwall.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/controller/passwall.lua @@ -496,6 +496,27 @@ function delete_select_nodes() uci:delete(appname, t[".name"], "to_node") uci:delete(appname, t[".name"], "chain_proxy") end + local list_name = t["urltest_node"] and "urltest_node" or (t["balancing_node"] and "balancing_node") + if list_name then + local nodes = uci:get_list(appname, t[".name"], list_name) + if nodes then + local changed = false + local new_nodes = {} + for _, node in ipairs(nodes) do + if node ~= w then + table.insert(new_nodes, node) + else + changed = true + end + end + if changed then + uci:set_list(appname, t[".name"], list_name, new_nodes) + end + end + end + if t["fallback_node"] == w then + uci:delete(appname, t[".name"], "fallback_node") + end end) if (uci:get(appname, w, "add_mode") or "0") == "2" then local add_from = uci:get(appname, w, "add_from") or "" diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua index 0cd8ad95d9..c1e6440c51 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua @@ -61,6 +61,37 @@ function s.remove(e, t) m:set(s[".name"], "udp_node", "default") end end) + m.uci:foreach(appname, "nodes", function(s) + if s["preproxy_node"] == t then + m:del(s[".name"], "preproxy_node") + m:del(s[".name"], "chain_proxy") + end + if s["to_node"] == t then + m:del(s[".name"], "to_node") + m:del(s[".name"], "chain_proxy") + end + local list_name = s["urltest_node"] and "urltest_node" or (s["balancing_node"] and "balancing_node") + if list_name then + local nodes = m.uci:get_list(appname, s[".name"], list_name) + if nodes then + local changed = false + local new_nodes = {} + for _, node in ipairs(nodes) do + if node ~= t then + table.insert(new_nodes, node) + else + changed = true + end + end + if changed then + m.uci:set_list(appname, s[".name"], list_name, new_nodes) + end + end + end + if s["fallback_node"] == t then + m:del(s[".name"], "fallback_node") + end + end) if (m:get(t, "add_mode") or "0") == "2" then local add_from = m:get(t, "add_from") or "" if add_from ~= "" then diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua index d4f45b75e5..17485c97d1 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua @@ -241,6 +241,9 @@ if has_xray then end if has_singbox then + local version = api.get_app_version("sing-box"):match("[^v]+") + local version_ge_1_12_0 = api.compare_versions(version, ">=", "1.12.0") + s = m:section(TypedSection, "global_singbox", "Sing-Box " .. translate("Settings")) s.anonymous = true s.addremove = false @@ -249,6 +252,16 @@ if has_singbox then o.default = 0 o.rmempty = false o.description = translate("Override the connection destination address with the sniffed domain.
    When enabled, traffic will match only by domain, ignoring IP rules.
    If using shunt nodes, configure the domain shunt rules correctly.") + + if version_ge_1_12_0 then + o = s:option(Flag, "record_fragment", "TLS Record " .. translate("Fragment"), + translate("Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first.")) + o.default = 0 + + o = s:option(Flag, "fragment", "TLS TCP " .. translate("Fragment"), + translate("Split handshake into multiple TCP segments. Enhances obfuscation. May increase delay. Use only if needed.")) + o.default = 0 + end end return m diff --git a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua index b0058fb6bf..8e8f988c31 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -7,16 +7,16 @@ local appname = "passwall" local fs = api.fs local split = api.split -local local_version = api.get_app_version("sing-box") -local version_ge_1_11_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.11.0") -local version_ge_1_12_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.12.0") +local local_version = api.get_app_version("sing-box"):match("[^v]+") +local version_ge_1_11_0 = api.compare_versions(local_version, ">=", "1.11.0") +local version_ge_1_12_0 = api.compare_versions(local_version, ">=", "1.12.0") local geosite_all_tag = {} local geoip_all_tag = {} local srss_path = "/tmp/etc/" .. appname .."_tmp/srss/" local function convert_geofile() - if api.compare_versions(local_version:match("[^v]+"), "<", "1.8.0") then + if api.compare_versions(local_version, "<", "1.8.0") then api.log("!!!注意:Sing-Box 版本低,Sing-Box 分流无法启用!请在[组件更新]中更新。") return end @@ -81,9 +81,13 @@ function gen_outbound(flag, node, tag, proxy_table) end local proxy_tag = nil + local fragment = nil + local record_fragment = nil local run_socks_instance = true if proxy_table ~= nil and type(proxy_table) == "table" then proxy_tag = proxy_table.tag or nil + fragment = proxy_table.fragment or nil + record_fragment = proxy_table.record_fragment or nil run_socks_instance = proxy_table.run_socks_instance end @@ -157,6 +161,8 @@ function gen_outbound(flag, node, tag, proxy_table) alpn = alpn, --支持的应用层协议协商列表,按优先顺序排列。如果两个对等点都支持 ALPN,则选择的协议将是此列表中的一个,如果没有相互支持的协议则连接将失败。 --min_version = "1.2", --max_version = "1.3", + fragment = fragment, + record_fragment = record_fragment, ech = { enabled = (node.ech == "1") and true or false, config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {} @@ -373,6 +379,8 @@ function gen_outbound(flag, node, tag, proxy_table) enabled = true, server_name = node.tls_serverName, insecure = (node.tls_allowInsecure == "1") and true or false, + fragment = fragment, + record_fragment = record_fragment, alpn = (node.hysteria_alpn and node.hysteria_alpn ~= "") and { node.hysteria_alpn } or nil, @@ -405,6 +413,8 @@ function gen_outbound(flag, node, tag, proxy_table) enabled = true, server_name = node.tls_serverName, insecure = (node.tls_allowInsecure == "1") and true or false, + fragment = fragment, + record_fragment = record_fragment, alpn = (node.tuic_alpn and node.tuic_alpn ~= "") and { node.tuic_alpn } or nil, @@ -440,6 +450,8 @@ function gen_outbound(flag, node, tag, proxy_table) enabled = true, server_name = node.tls_serverName, insecure = (node.tls_allowInsecure == "1") and true or false, + fragment = fragment, + record_fragment = record_fragment, ech = { enabled = (node.ech == "1") and true or false, config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {} @@ -1016,7 +1028,7 @@ function gen_config(var) end if is_new_ut_node then local ut_node = uci:get_all(appname, ut_node_id) - local outbound = gen_outbound(flag, ut_node, ut_node_tag, { run_socks_instance = not no_run }) + local outbound = gen_outbound(flag, ut_node, ut_node_tag, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run }) if outbound then outbound.tag = outbound.tag .. ":" .. ut_node.remarks table.insert(outbounds, outbound) @@ -1182,8 +1194,19 @@ function gen_config(var) }) end end - - local _outbound = gen_outbound(flag, _node, rule_name, { tag = use_proxy and preproxy_tag or nil, run_socks_instance = not no_run }) + local proxy_table = { + tag = use_proxy and preproxy_tag or nil, + run_socks_instance = not no_run + } + if not proxy_table.tag then + if singbox_settings.fragment == "1" then + proxy_table.fragment = true + end + if singbox_settings.record_fragment == "1" then + proxy_table.record_fragment = true + end + end + local _outbound = gen_outbound(flag, _node, rule_name, proxy_table) if _outbound then _outbound.tag = _outbound.tag .. ":" .. _node.remarks rule_outboundTag, last_insert_outbound = set_outbound_detour(_node, _outbound, outbounds, rule_name) @@ -1432,7 +1455,7 @@ function gen_config(var) sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface)) end else - local outbound = gen_outbound(flag, node, nil, { run_socks_instance = not no_run }) + local outbound = gen_outbound(flag, node, nil, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run }) if outbound then outbound.tag = outbound.tag .. ":" .. node.remarks COMMON.default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds) diff --git a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua index c7876e75c0..2fba58720d 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -1151,7 +1151,7 @@ function gen_config(var) sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface)) end else - local outbound = gen_outbound(flag, node, nil, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.fragment == "1" or nil, run_socks_instance = not no_run }) + local outbound = gen_outbound(flag, node, nil, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run }) if outbound then outbound.tag = outbound.tag .. ":" .. node.remarks COMMON.default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds) diff --git a/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po b/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po index 154b3e67c2..afb3c3d8e1 100644 --- a/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po +++ b/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po @@ -1750,6 +1750,12 @@ msgstr "分片间隔" msgid "Fragmentation interval (ms)" msgstr "分片间隔(ms)" +msgid "Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first." +msgstr 将握手数据拆分为多个 TLS 记录,提升抗封锁能力,几乎不增加延迟,建议优先启用。" + +msgid "Split handshake into multiple TCP segments. Enhances obfuscation. May increase delay. Use only if needed." +msgstr "将 TLS 握手数据分为多个 TCP 包发送,提高伪装性,可能增加延迟,仅在封锁严重时使用。" + msgid "Noise" msgstr "噪声" diff --git a/sing-box/clients/android/app/build.gradle b/sing-box/clients/android/app/build.gradle index 2329050306..159c126675 100644 --- a/sing-box/clients/android/app/build.gradle +++ b/sing-box/clients/android/app/build.gradle @@ -105,19 +105,19 @@ dependencies { implementation "androidx.constraintlayout:constraintlayout:2.2.1" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.9.1" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.1" - implementation "androidx.navigation:navigation-fragment-ktx:2.9.0" - implementation "androidx.navigation:navigation-ui-ktx:2.9.0" + implementation "androidx.navigation:navigation-fragment-ktx:2.9.1" + implementation "androidx.navigation:navigation-ui-ktx:2.9.1" implementation "com.google.zxing:core:3.5.3" - implementation "androidx.room:room-runtime:2.7.1" + implementation "androidx.room:room-runtime:2.7.2" implementation "androidx.coordinatorlayout:coordinatorlayout:1.3.0" implementation "androidx.preference:preference-ktx:1.2.1" implementation "androidx.camera:camera-view:1.4.2" implementation "androidx.camera:camera-lifecycle:1.4.2" implementation "androidx.camera:camera-camera2:1.4.2" - ksp "androidx.room:room-compiler:2.7.1" - implementation "androidx.work:work-runtime-ktx:2.10.1" + ksp "androidx.room:room-compiler:2.7.2" + implementation "androidx.work:work-runtime-ktx:2.10.2" implementation "androidx.browser:browser:1.8.0" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2" // DO NOT UPDATE (minSdkVersion updated) implementation "com.blacksquircle.ui:editorkit:2.2.0" @@ -126,7 +126,7 @@ dependencies { implementation("com.android.tools.smali:smali-dexlib2:3.0.9") { exclude group: "com.google.guava", module: "guava" } - implementation "com.google.guava:guava:33.0.0-android" + implementation "com.google.guava:guava:33.4.8-android" playImplementation "com.google.android.play:app-update-ktx:2.1.0" playImplementation "com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1" } diff --git a/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt b/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt index 36264e8735..fadfad603d 100644 --- a/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt +++ b/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt @@ -150,7 +150,6 @@ class BoxService( } DefaultNetworkMonitor.start() - Libbox.registerLocalDNSTransport(LocalResolver) Libbox.setMemoryLimit(!Settings.disableMemoryLimit) val newService = try { @@ -263,7 +262,6 @@ class BoxService( } commandServer?.setService(null) boxService = null - Libbox.registerLocalDNSTransport(null) DefaultNetworkMonitor.stop() commandServer?.apply { diff --git a/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/PlatformInterfaceWrapper.kt b/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/PlatformInterfaceWrapper.kt index a6c6fe0af4..068a529c9b 100644 --- a/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/PlatformInterfaceWrapper.kt +++ b/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/PlatformInterfaceWrapper.kt @@ -10,6 +10,7 @@ import android.util.Log import androidx.annotation.RequiresApi import io.nekohasekai.libbox.InterfaceUpdateListener import io.nekohasekai.libbox.Libbox +import io.nekohasekai.libbox.LocalDNSTransport import io.nekohasekai.libbox.NetworkInterfaceIterator import io.nekohasekai.libbox.PlatformInterface import io.nekohasekai.libbox.StringIterator @@ -20,6 +21,9 @@ import java.net.Inet6Address import java.net.InetSocketAddress import java.net.InterfaceAddress import java.net.NetworkInterface +import java.security.KeyStore +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi import io.nekohasekai.libbox.NetworkInterface as LibboxNetworkInterface interface PlatformInterfaceWrapper : PlatformInterface { @@ -169,6 +173,27 @@ interface PlatformInterfaceWrapper : PlatformInterface { return WIFIState(ssid, wifiInfo.bssid) } + override fun localDNSTransport(): LocalDNSTransport? { + return LocalResolver + } + + @OptIn(ExperimentalEncodingApi::class) + override fun systemCertificates(): StringIterator { + val certificates = mutableListOf() + val keyStore = KeyStore.getInstance("AndroidCAStore") + if (keyStore != null) { + keyStore.load(null, null); + val aliases = keyStore.aliases() + while (aliases.hasMoreElements()) { + val cert = keyStore.getCertificate(aliases.nextElement()) + certificates.add( + "-----BEGIN CERTIFICATE-----\n" + Base64.encode(cert.encoded) + "\n-----END CERTIFICATE-----" + ) + } + } + return StringArray(certificates.iterator()) + } + private class InterfaceArray(private val iterator: Iterator) : NetworkInterfaceIterator { diff --git a/sing-box/clients/android/build.gradle b/sing-box/clients/android/build.gradle index 494700f586..135472c2ac 100644 --- a/sing-box/clients/android/build.gradle +++ b/sing-box/clients/android/build.gradle @@ -5,8 +5,8 @@ buildscript { } plugins { - id 'com.android.application' version '8.10.1' apply false - id 'com.android.library' version '8.10.1' apply false + id 'com.android.application' version '8.11.0' apply false + id 'com.android.library' version '8.11.0' apply false id 'org.jetbrains.kotlin.android' version '2.1.0' apply false id 'com.google.devtools.ksp' version '2.1.0-1.0.29' apply false id 'com.github.triplet.play' version '3.8.4' apply false diff --git a/sing-box/clients/android/gradle/wrapper/gradle-wrapper.properties b/sing-box/clients/android/gradle/wrapper/gradle-wrapper.properties index dd6d92592f..cfc2446177 100644 --- a/sing-box/clients/android/gradle/wrapper/gradle-wrapper.properties +++ b/sing-box/clients/android/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ -#Tue May 27 14:17:19 CST 2025 +#Mon Jul 07 14:05:29 CST 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/sing-box/clients/android/version.properties b/sing-box/clients/android/version.properties index 21a6675b0a..31e219f2a3 100644 --- a/sing-box/clients/android/version.properties +++ b/sing-box/clients/android/version.properties @@ -1,3 +1,3 @@ -VERSION_CODE=542 -VERSION_NAME=1.11.15 -GO_VERSION=go1.24.4 +VERSION_CODE=549 +VERSION_NAME=1.12.0 +GO_VERSION=go1.24.5 diff --git a/sing-box/docs/changelog.md b/sing-box/docs/changelog.md index db9f76aaeb..1544deb65e 100644 --- a/sing-box/docs/changelog.md +++ b/sing-box/docs/changelog.md @@ -23,6 +23,7 @@ icon: material/alert-decagram * Improve tun performance on Apple platforms **17** * Update quic-go to v0.52.0 * Update gVisor to 20250319.0 +* Update the status of graphical clients in stores **18** **1**: @@ -146,6 +147,12 @@ The following data was tested using [tun_bench](https://github.com/SagerNet/sing | 1.11.15 | system | 65535 | 26.2G | 17.4G | | 1.12.0-rc.4 | system | 65535 | 17.6G | 21.0G | +**18**: + +We continue to experience issues updating our sing-box apps on the App Store and Play Store. +Until we rewrite and resubmit the apps, they are considered irrecoverable. +Therefore, after this release, we will not be repeating this notice unless there is new information. + ### 1.11.15 * Fixes and improvements diff --git a/small/luci-app-passwall/luasrc/controller/passwall.lua b/small/luci-app-passwall/luasrc/controller/passwall.lua index f4c6393a88..2d62b8dcbb 100644 --- a/small/luci-app-passwall/luasrc/controller/passwall.lua +++ b/small/luci-app-passwall/luasrc/controller/passwall.lua @@ -496,6 +496,27 @@ function delete_select_nodes() uci:delete(appname, t[".name"], "to_node") uci:delete(appname, t[".name"], "chain_proxy") end + local list_name = t["urltest_node"] and "urltest_node" or (t["balancing_node"] and "balancing_node") + if list_name then + local nodes = uci:get_list(appname, t[".name"], list_name) + if nodes then + local changed = false + local new_nodes = {} + for _, node in ipairs(nodes) do + if node ~= w then + table.insert(new_nodes, node) + else + changed = true + end + end + if changed then + uci:set_list(appname, t[".name"], list_name, new_nodes) + end + end + end + if t["fallback_node"] == w then + uci:delete(appname, t[".name"], "fallback_node") + end end) if (uci:get(appname, w, "add_mode") or "0") == "2" then local add_from = uci:get(appname, w, "add_from") or "" diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua index 0cd8ad95d9..c1e6440c51 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua @@ -61,6 +61,37 @@ function s.remove(e, t) m:set(s[".name"], "udp_node", "default") end end) + m.uci:foreach(appname, "nodes", function(s) + if s["preproxy_node"] == t then + m:del(s[".name"], "preproxy_node") + m:del(s[".name"], "chain_proxy") + end + if s["to_node"] == t then + m:del(s[".name"], "to_node") + m:del(s[".name"], "chain_proxy") + end + local list_name = s["urltest_node"] and "urltest_node" or (s["balancing_node"] and "balancing_node") + if list_name then + local nodes = m.uci:get_list(appname, s[".name"], list_name) + if nodes then + local changed = false + local new_nodes = {} + for _, node in ipairs(nodes) do + if node ~= t then + table.insert(new_nodes, node) + else + changed = true + end + end + if changed then + m.uci:set_list(appname, s[".name"], list_name, new_nodes) + end + end + end + if s["fallback_node"] == t then + m:del(s[".name"], "fallback_node") + end + end) if (m:get(t, "add_mode") or "0") == "2" then local add_from = m:get(t, "add_from") or "" if add_from ~= "" then diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua index d4f45b75e5..17485c97d1 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua @@ -241,6 +241,9 @@ if has_xray then end if has_singbox then + local version = api.get_app_version("sing-box"):match("[^v]+") + local version_ge_1_12_0 = api.compare_versions(version, ">=", "1.12.0") + s = m:section(TypedSection, "global_singbox", "Sing-Box " .. translate("Settings")) s.anonymous = true s.addremove = false @@ -249,6 +252,16 @@ if has_singbox then o.default = 0 o.rmempty = false o.description = translate("Override the connection destination address with the sniffed domain.
    When enabled, traffic will match only by domain, ignoring IP rules.
    If using shunt nodes, configure the domain shunt rules correctly.") + + if version_ge_1_12_0 then + o = s:option(Flag, "record_fragment", "TLS Record " .. translate("Fragment"), + translate("Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first.")) + o.default = 0 + + o = s:option(Flag, "fragment", "TLS TCP " .. translate("Fragment"), + translate("Split handshake into multiple TCP segments. Enhances obfuscation. May increase delay. Use only if needed.")) + o.default = 0 + end end return m diff --git a/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua index b0058fb6bf..8e8f988c31 100644 --- a/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/small/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -7,16 +7,16 @@ local appname = "passwall" local fs = api.fs local split = api.split -local local_version = api.get_app_version("sing-box") -local version_ge_1_11_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.11.0") -local version_ge_1_12_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.12.0") +local local_version = api.get_app_version("sing-box"):match("[^v]+") +local version_ge_1_11_0 = api.compare_versions(local_version, ">=", "1.11.0") +local version_ge_1_12_0 = api.compare_versions(local_version, ">=", "1.12.0") local geosite_all_tag = {} local geoip_all_tag = {} local srss_path = "/tmp/etc/" .. appname .."_tmp/srss/" local function convert_geofile() - if api.compare_versions(local_version:match("[^v]+"), "<", "1.8.0") then + if api.compare_versions(local_version, "<", "1.8.0") then api.log("!!!注意:Sing-Box 版本低,Sing-Box 分流无法启用!请在[组件更新]中更新。") return end @@ -81,9 +81,13 @@ function gen_outbound(flag, node, tag, proxy_table) end local proxy_tag = nil + local fragment = nil + local record_fragment = nil local run_socks_instance = true if proxy_table ~= nil and type(proxy_table) == "table" then proxy_tag = proxy_table.tag or nil + fragment = proxy_table.fragment or nil + record_fragment = proxy_table.record_fragment or nil run_socks_instance = proxy_table.run_socks_instance end @@ -157,6 +161,8 @@ function gen_outbound(flag, node, tag, proxy_table) alpn = alpn, --支持的应用层协议协商列表,按优先顺序排列。如果两个对等点都支持 ALPN,则选择的协议将是此列表中的一个,如果没有相互支持的协议则连接将失败。 --min_version = "1.2", --max_version = "1.3", + fragment = fragment, + record_fragment = record_fragment, ech = { enabled = (node.ech == "1") and true or false, config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {} @@ -373,6 +379,8 @@ function gen_outbound(flag, node, tag, proxy_table) enabled = true, server_name = node.tls_serverName, insecure = (node.tls_allowInsecure == "1") and true or false, + fragment = fragment, + record_fragment = record_fragment, alpn = (node.hysteria_alpn and node.hysteria_alpn ~= "") and { node.hysteria_alpn } or nil, @@ -405,6 +413,8 @@ function gen_outbound(flag, node, tag, proxy_table) enabled = true, server_name = node.tls_serverName, insecure = (node.tls_allowInsecure == "1") and true or false, + fragment = fragment, + record_fragment = record_fragment, alpn = (node.tuic_alpn and node.tuic_alpn ~= "") and { node.tuic_alpn } or nil, @@ -440,6 +450,8 @@ function gen_outbound(flag, node, tag, proxy_table) enabled = true, server_name = node.tls_serverName, insecure = (node.tls_allowInsecure == "1") and true or false, + fragment = fragment, + record_fragment = record_fragment, ech = { enabled = (node.ech == "1") and true or false, config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {} @@ -1016,7 +1028,7 @@ function gen_config(var) end if is_new_ut_node then local ut_node = uci:get_all(appname, ut_node_id) - local outbound = gen_outbound(flag, ut_node, ut_node_tag, { run_socks_instance = not no_run }) + local outbound = gen_outbound(flag, ut_node, ut_node_tag, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run }) if outbound then outbound.tag = outbound.tag .. ":" .. ut_node.remarks table.insert(outbounds, outbound) @@ -1182,8 +1194,19 @@ function gen_config(var) }) end end - - local _outbound = gen_outbound(flag, _node, rule_name, { tag = use_proxy and preproxy_tag or nil, run_socks_instance = not no_run }) + local proxy_table = { + tag = use_proxy and preproxy_tag or nil, + run_socks_instance = not no_run + } + if not proxy_table.tag then + if singbox_settings.fragment == "1" then + proxy_table.fragment = true + end + if singbox_settings.record_fragment == "1" then + proxy_table.record_fragment = true + end + end + local _outbound = gen_outbound(flag, _node, rule_name, proxy_table) if _outbound then _outbound.tag = _outbound.tag .. ":" .. _node.remarks rule_outboundTag, last_insert_outbound = set_outbound_detour(_node, _outbound, outbounds, rule_name) @@ -1432,7 +1455,7 @@ function gen_config(var) sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface)) end else - local outbound = gen_outbound(flag, node, nil, { run_socks_instance = not no_run }) + local outbound = gen_outbound(flag, node, nil, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run }) if outbound then outbound.tag = outbound.tag .. ":" .. node.remarks COMMON.default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds) diff --git a/small/luci-app-passwall/luasrc/passwall/util_xray.lua b/small/luci-app-passwall/luasrc/passwall/util_xray.lua index c7876e75c0..2fba58720d 100644 --- a/small/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/small/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -1151,7 +1151,7 @@ function gen_config(var) sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface)) end else - local outbound = gen_outbound(flag, node, nil, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.fragment == "1" or nil, run_socks_instance = not no_run }) + local outbound = gen_outbound(flag, node, nil, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run }) if outbound then outbound.tag = outbound.tag .. ":" .. node.remarks COMMON.default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds) diff --git a/small/luci-app-passwall/po/zh-cn/passwall.po b/small/luci-app-passwall/po/zh-cn/passwall.po index 154b3e67c2..afb3c3d8e1 100644 --- a/small/luci-app-passwall/po/zh-cn/passwall.po +++ b/small/luci-app-passwall/po/zh-cn/passwall.po @@ -1750,6 +1750,12 @@ msgstr "分片间隔" msgid "Fragmentation interval (ms)" msgstr "分片间隔(ms)" +msgid "Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first." +msgstr 将握手数据拆分为多个 TLS 记录,提升抗封锁能力,几乎不增加延迟,建议优先启用。" + +msgid "Split handshake into multiple TCP segments. Enhances obfuscation. May increase delay. Use only if needed." +msgstr "将 TLS 握手数据分为多个 TCP 包发送,提高伪装性,可能增加延迟,仅在封锁严重时使用。" + msgid "Noise" msgstr "噪声" diff --git a/small/nikki/files/nikki.init b/small/nikki/files/nikki.init index 4d7326ad1b..c51b66252f 100644 --- a/small/nikki/files/nikki.init +++ b/small/nikki/files/nikki.init @@ -4,7 +4,8 @@ START=99 STOP=10 USE_PROCD=1 -. "$IPKG_INSTROOT/lib/functions/network.sh" +PROG="/usr/bin/mihomo" + . "$IPKG_INSTROOT/etc/nikki/scripts/include.sh" extra_command 'update_subscription' 'Update subscription by section id' @@ -193,9 +194,9 @@ service_started() { # load config config_load nikki # check if proxy enabled - local enabled - config_get_bool enabled "proxy" "enabled" 0 - if [ "$enabled" = 0 ]; then + local proxy_enabled + config_get_bool proxy_enabled "proxy" "enabled" 0 + if [ "$proxy_enabled" = 0 ]; then log "Proxy" "Disabled." return fi diff --git a/small/nikki/files/scripts/firewall_include.sh b/small/nikki/files/scripts/firewall_include.sh index 4e05fce0c9..f270f0e425 100644 --- a/small/nikki/files/scripts/firewall_include.sh +++ b/small/nikki/files/scripts/firewall_include.sh @@ -4,12 +4,14 @@ . "$IPKG_INSTROOT/etc/nikki/scripts/include.sh" config_load nikki -config_get enabled "config" "enabled" 0 +config_get_bool enabled "config" "enabled" 0 +config_get_bool core_only "config" "core_only" 0 +config_get_bool proxy_enabled "proxy" "enabled" 0 config_get tcp_mode "proxy" "tcp_mode" config_get udp_mode "proxy" "udp_mode" config_get tun_device "mixin" "tun_device" -if [ "$enabled" = 1 ]; then +if [ "$enabled" = 1 ] && [ "$core_only" = 0 ] && [ "$proxy_enabled" = 1 ]; then if [ "$tcp_mode" = "tun" ] || [ "$udp_mode" = "tun" ]; then nft insert rule inet fw4 input iifname "$tun_device" counter accept comment "nikki" nft insert rule inet fw4 forward oifname "$tun_device" counter accept comment "nikki" diff --git a/small/nikki/files/scripts/include.sh b/small/nikki/files/scripts/include.sh index 502f271da1..918c139026 100644 --- a/small/nikki/files/scripts/include.sh +++ b/small/nikki/files/scripts/include.sh @@ -1,7 +1,6 @@ #!/bin/sh # paths -PROG="/usr/bin/mihomo" HOME_DIR="/etc/nikki" PROFILES_DIR="$HOME_DIR/profiles" SUBSCRIPTIONS_DIR="$HOME_DIR/subscriptions" @@ -26,7 +25,7 @@ BRIDGE_NF_CALL_IP6TABLES_FLAG_PATH="$TEMP_DIR/bridge_nf_call_ip6tables.flag" # ucode UCODE_DIR="$HOME_DIR/ucode" -INCLUDE_UCODE="$UCODE_DIR/include.uc" +INCLUDE_UC="$UCODE_DIR/include.uc" MIXIN_UC="$UCODE_DIR/mixin.uc" HIJACK_UT="$UCODE_DIR/hijack.ut" @@ -44,26 +43,27 @@ GEOIP6_CN_NFT="$NFT_DIR/geoip6_cn.nft" # functions format_filesize() { - local kb; kb=1024 + local b; b=1 + local kb; kb=$((b * 1024)) local mb; mb=$((kb * 1024)) local gb; gb=$((mb * 1024)) local tb; tb=$((gb * 1024)) local pb; pb=$((tb * 1024)) local size; size="$1" - if [ -z "$size" ]; then - echo "" - elif [ "$size" -lt "$kb" ]; then - echo "$size B" - elif [ "$size" -lt "$mb" ]; then - echo "$(awk "BEGIN {print $size / $kb}") KB" - elif [ "$size" -lt "$gb" ]; then - echo "$(awk "BEGIN {print $size / $mb}") MB" - elif [ "$size" -lt "$tb" ]; then - echo "$(awk "BEGIN {print $size / $gb}") GB" - elif [ "$size" -lt "$pb" ]; then - echo "$(awk "BEGIN {print $size / $tb}") TB" - else - echo "$(awk "BEGIN {print $size / $pb}") PB" + if [ -n "$size" ]; then + if [ "$size" -lt "$kb" ]; then + echo "$(awk "BEGIN {print $size / $b}") B" + elif [ "$size" -lt "$mb" ]; then + echo "$(awk "BEGIN {print $size / $kb}") KB" + elif [ "$size" -lt "$gb" ]; then + echo "$(awk "BEGIN {print $size / $mb}") MB" + elif [ "$size" -lt "$tb" ]; then + echo "$(awk "BEGIN {print $size / $gb}") GB" + elif [ "$size" -lt "$pb" ]; then + echo "$(awk "BEGIN {print $size / $tb}") TB" + else + echo "$(awk "BEGIN {print $size / $pb}") PB" + fi fi } diff --git a/v2rayn/v2rayN/Directory.Build.props b/v2rayn/v2rayN/Directory.Build.props index 484cde4740..901b15d84d 100644 --- a/v2rayn/v2rayN/Directory.Build.props +++ b/v2rayn/v2rayN/Directory.Build.props @@ -1,7 +1,7 @@ - 7.13.5 + 7.13.6 diff --git a/v2rayn/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayn/v2rayN/ServiceLib/Handler/ConfigHandler.cs index c08ca28e80..0eeadc466d 100644 --- a/v2rayn/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayn/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -246,6 +246,7 @@ public class ConfigHandler item.PublicKey = profileItem.PublicKey; item.ShortId = profileItem.ShortId; item.SpiderX = profileItem.SpiderX; + item.Mldsa65Verify = profileItem.Mldsa65Verify; item.Extra = profileItem.Extra; item.MuxEnabled = profileItem.MuxEnabled; } diff --git a/v2rayn/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs b/v2rayn/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs index 86b92fa0f3..fa84a411d1 100644 --- a/v2rayn/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs +++ b/v2rayn/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs @@ -59,6 +59,10 @@ public class BaseFmt { dicQuery.Add("spx", Utils.UrlEncode(item.SpiderX)); } + if (item.Mldsa65Verify.IsNotEmpty()) + { + dicQuery.Add("pqv", Utils.UrlEncode(item.Mldsa65Verify)); + } if (item.AllowInsecure.Equals("true")) { dicQuery.Add("allowInsecure", "1"); @@ -159,6 +163,7 @@ public class BaseFmt item.PublicKey = Utils.UrlDecode(query["pbk"] ?? ""); item.ShortId = Utils.UrlDecode(query["sid"] ?? ""); item.SpiderX = Utils.UrlDecode(query["spx"] ?? ""); + item.Mldsa65Verify = Utils.UrlDecode(query["pqv"] ?? ""); item.AllowInsecure = (query["allowInsecure"] ?? "") == "1" ? "true" : ""; item.Network = query["type"] ?? nameof(ETransport.tcp); diff --git a/v2rayn/v2rayN/ServiceLib/Models/ProfileItem.cs b/v2rayn/v2rayN/ServiceLib/Models/ProfileItem.cs index 077bc4b27e..998aa1201a 100644 --- a/v2rayn/v2rayN/ServiceLib/Models/ProfileItem.cs +++ b/v2rayn/v2rayN/ServiceLib/Models/ProfileItem.cs @@ -93,6 +93,7 @@ public class ProfileItem : ReactiveObject public string PublicKey { get; set; } public string ShortId { get; set; } public string SpiderX { get; set; } + public string Mldsa65Verify { get; set; } public string Extra { get; set; } public bool? MuxEnabled { get; set; } } diff --git a/v2rayn/v2rayN/ServiceLib/Models/V2rayConfig.cs b/v2rayn/v2rayN/ServiceLib/Models/V2rayConfig.cs index 2e5ed4b47b..e7cd3722d9 100644 --- a/v2rayn/v2rayN/ServiceLib/Models/V2rayConfig.cs +++ b/v2rayn/v2rayN/ServiceLib/Models/V2rayConfig.cs @@ -340,6 +340,7 @@ public class TlsSettings4Ray public string? publicKey { get; set; } public string? shortId { get; set; } public string? spiderX { get; set; } + public string? mldsa65Verify { get; set; } } public class TcpSettings4Ray diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 234697634c..992adb9408 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -2517,6 +2517,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Mldsa65Verify 的本地化字符串。 + /// + public static string TbMldsa65Verify { + get { + return ResourceManager.GetString("TbMldsa65Verify", resourceCulture); + } + } + /// /// 查找类似 Transport protocol(network) 的本地化字符串。 /// diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 1f05cf8f21..eb9ae271ed 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1398,4 +1398,7 @@ Incorrect password, please try again. + + Mldsa65Verify + \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.hu.resx index d7fd9fd30f..0d45540e30 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1398,4 +1398,7 @@ Helytelen jelszó, próbálja újra. - + + Mldsa65Verify + + \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.resx index 2826c9cc5e..03e9b124c7 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1398,4 +1398,7 @@ Incorrect password, please try again. + + Mldsa65Verify + \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx index a4d8212799..75a596aa28 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1398,4 +1398,7 @@ Incorrect password, please try again. + + Mldsa65Verify + \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 5518682218..8766ca8f1b 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1395,4 +1395,7 @@ 密码错误,请重试。 + + Mldsa65Verify + \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index cbf27b8c21..9c7a2a3cf6 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1395,4 +1395,7 @@ 密碼錯誤,請重試。 + + Mldsa65Verify + \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index 9be1acf043..2e4f584253 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -944,6 +944,7 @@ public class CoreConfigV2rayService publicKey = node.PublicKey, shortId = node.ShortId, spiderX = node.SpiderX, + mldsa65Verify = node.Mldsa65Verify, show = false, }; diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index ad30f9856d..da56d617e8 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -753,7 +753,7 @@ Grid.Row="7" ColumnDefinitions="180,Auto" IsVisible="False" - RowDefinitions="Auto,Auto,Auto,Auto,Auto"> + RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto"> + + + diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs b/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs index 5b86143b13..3adef60398 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs +++ b/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs @@ -185,6 +185,7 @@ public partial class AddServerWindow : WindowBase this.Bind(ViewModel, vm => vm.SelectedSource.PublicKey, v => v.txtPublicKey.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables); }); diff --git a/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml b/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml index 3b7be6800f..9a4b88ad8f 100644 --- a/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml +++ b/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml @@ -979,6 +979,7 @@ + @@ -1064,6 +1065,22 @@ Margin="{StaticResource Margin4}" HorizontalAlignment="Left" Style="{StaticResource DefTextBox}" /> + + + vm.SelectedSource.PublicKey, v => v.txtPublicKey.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables); }); diff --git a/v2rayng/V2rayNG/app/build.gradle.kts b/v2rayng/V2rayNG/app/build.gradle.kts index 7700dba822..0938278d87 100644 --- a/v2rayng/V2rayNG/app/build.gradle.kts +++ b/v2rayng/V2rayNG/app/build.gradle.kts @@ -12,8 +12,8 @@ android { applicationId = "com.v2ray.ang" minSdk = 21 targetSdk = 35 - versionCode = 661 - versionName = "1.10.11" + versionCode = 662 + versionName = "1.10.12" multiDexEnabled = true val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';') diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ProfileItem.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ProfileItem.kt index 7a1f734633..16e4620bdd 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ProfileItem.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ProfileItem.kt @@ -44,6 +44,7 @@ data class ProfileItem( var publicKey: String? = null, var shortId: String? = null, var spiderX: String? = null, + var mldsa65Verify: String? = null, var secretKey: String? = null, var preSharedKey: String? = null, diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/V2rayConfig.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/V2rayConfig.kt index 4393bd70f5..9aa161ec2c 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/V2rayConfig.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/V2rayConfig.kt @@ -264,7 +264,8 @@ data class V2rayConfig( val show: Boolean = false, var publicKey: String? = null, var shortId: String? = null, - var spiderX: String? = null + var spiderX: String? = null, + var mldsa65Verify: String? = null ) data class QuicSettingBean( diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt index 73cdf958af..525e3da501 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt @@ -83,6 +83,7 @@ open class FmtBase { config.publicKey = queryParam["pbk"] config.shortId = queryParam["sid"] config.spiderX = queryParam["spx"] + config.mldsa65Verify = queryParam["pqv"] config.flow = queryParam["flow"] } @@ -101,6 +102,7 @@ open class FmtBase { config.publicKey.let { if (it.isNotNullEmpty()) dicQuery["pbk"] = it.orEmpty() } config.shortId.let { if (it.isNotNullEmpty()) dicQuery["sid"] = it.orEmpty() } config.spiderX.let { if (it.isNotNullEmpty()) dicQuery["spx"] = it.orEmpty() } + config.mldsa65Verify.let { if (it.isNotNullEmpty()) dicQuery["pqv"] = it.orEmpty() } config.flow.let { if (it.isNotNullEmpty()) dicQuery["flow"] = it.orEmpty() } val networkType = NetworkType.fromString(config.network) diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt index 960992442d..332c8258ac 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt @@ -1235,6 +1235,7 @@ object V2rayConfigManager { val publicKey = profileItem.publicKey val shortId = profileItem.shortId val spiderX = profileItem.spiderX + val mldsa65Verify = profileItem.mldsa65Verify streamSettings.security = if (streamSecurity.isEmpty()) null else streamSecurity if (streamSettings.security == null) return @@ -1246,6 +1247,7 @@ object V2rayConfigManager { publicKey = if (publicKey.isNullOrEmpty()) null else publicKey, shortId = if (shortId.isNullOrEmpty()) null else shortId, spiderX = if (spiderX.isNullOrEmpty()) null else spiderX, + mldsa65Verify = if (mldsa65Verify.isNullOrEmpty()) null else mldsa65Verify, ) if (streamSettings.security == AppConfig.TLS) { streamSettings.tlsSettings = tlsSetting diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerActivity.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerActivity.kt index e9bdad663d..2736edc9c7 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerActivity.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerActivity.kt @@ -117,6 +117,8 @@ class ServerActivity : BaseActivity() { private val container_short_id: LinearLayout? by lazy { findViewById(R.id.lay_short_id) } private val et_spider_x: EditText? by lazy { findViewById(R.id.et_spider_x) } private val container_spider_x: LinearLayout? by lazy { findViewById(R.id.lay_spider_x) } + private val et_mldsa65_verify: EditText? by lazy { findViewById(R.id.et_mldsa65_verify) } + private val container_mldsa65_verify: LinearLayout? by lazy { findViewById(R.id.lay_mldsa65_verify) } private val et_reserved1: EditText? by lazy { findViewById(R.id.et_reserved1) } private val et_local_address: EditText? by lazy { findViewById(R.id.et_local_address) } private val et_local_mtu: EditText? by lazy { findViewById(R.id.et_local_mtu) } @@ -253,9 +255,14 @@ class ServerActivity : BaseActivity() { // Case 1: Null or blank isBlank -> { listOf( - container_sni, container_fingerprint, container_alpn, - container_allow_insecure, container_public_key, - container_short_id, container_spider_x + container_sni, + container_fingerprint, + container_alpn, + container_allow_insecure, + container_public_key, + container_short_id, + container_spider_x, + container_mldsa65_verify ).forEach { it?.visibility = View.GONE } } @@ -270,7 +277,8 @@ class ServerActivity : BaseActivity() { listOf( container_public_key, container_short_id, - container_spider_x + container_spider_x, + container_mldsa65_verify ).forEach { it?.visibility = View.GONE } } @@ -284,7 +292,8 @@ class ServerActivity : BaseActivity() { listOf( container_public_key, container_short_id, - container_spider_x + container_spider_x, + container_mldsa65_verify ).forEach { it?.visibility = View.VISIBLE } } } @@ -366,9 +375,12 @@ class ServerActivity : BaseActivity() { if (allowinsecure >= 0) { sp_allow_insecure?.setSelection(allowinsecure) } - container_public_key?.visibility = View.GONE - container_short_id?.visibility = View.GONE - container_spider_x?.visibility = View.GONE + listOf( + container_public_key, + container_short_id, + container_spider_x, + container_mldsa65_verify + ).forEach { it?.visibility = View.GONE } } else if (config.security == REALITY) { container_public_key?.visibility = View.VISIBLE et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty()) @@ -376,18 +388,23 @@ class ServerActivity : BaseActivity() { et_short_id?.text = Utils.getEditable(config.shortId.orEmpty()) container_spider_x?.visibility = View.VISIBLE et_spider_x?.text = Utils.getEditable(config.spiderX.orEmpty()) + container_mldsa65_verify?.visibility = View.VISIBLE + et_mldsa65_verify?.text = Utils.getEditable(config.mldsa65Verify.orEmpty()) container_allow_insecure?.visibility = View.GONE } } if (config.security.isNullOrEmpty()) { - container_sni?.visibility = View.GONE - container_fingerprint?.visibility = View.GONE - container_alpn?.visibility = View.GONE - container_allow_insecure?.visibility = View.GONE - container_public_key?.visibility = View.GONE - container_short_id?.visibility = View.GONE - container_spider_x?.visibility = View.GONE + listOf( + container_sni, + container_fingerprint, + container_alpn, + container_allow_insecure, + container_public_key, + container_short_id, + container_spider_x, + container_mldsa65_verify + ).forEach { it?.visibility = View.GONE } } val network = Utils.arrayFind(networks, config.network.orEmpty()) if (network >= 0) { @@ -550,6 +567,7 @@ class ServerActivity : BaseActivity() { val publicKey = et_public_key?.text?.toString() val shortId = et_short_id?.text?.toString() val spiderX = et_spider_x?.text?.toString() + val mldsa65Verify = et_mldsa65_verify?.text?.toString() val allowInsecure = if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) { @@ -566,6 +584,7 @@ class ServerActivity : BaseActivity() { config.publicKey = publicKey config.shortId = shortId config.spiderX = spiderX + config.mldsa65Verify = mldsa65Verify } private fun transportTypes(network: String?): Array { diff --git a/v2rayng/V2rayNG/app/src/main/res/layout/layout_tls.xml b/v2rayng/V2rayNG/app/src/main/res/layout/layout_tls.xml index b7c34a1278..1866660f1e 100644 --- a/v2rayng/V2rayNG/app/src/main/res/layout/layout_tls.xml +++ b/v2rayng/V2rayNG/app/src/main/res/layout/layout_tls.xml @@ -178,4 +178,25 @@ android:nextFocusDown="@+id/sp_stream_fingerprint" /> + + + + + + + + \ No newline at end of file diff --git a/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml index 3067fd81c6..152cac9b21 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml @@ -80,6 +80,7 @@ PreSharedKey(optional) শর্ট আইডি SpiderX + Mldsa65Verify সিক্রেট কী সংরক্ষিত (ঐচ্ছিক) স্থানীয় ঠিকানা (ঐচ্ছিক IPv4/IPv6, কমা দ্বারা পৃথক করা) diff --git a/v2rayng/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml index a75fc78e44..3c1fa3023b 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml @@ -80,6 +80,7 @@ کیلیت رزم ناهاڌن ازاف (اختیاری) ShortID SpiderX + Mldsa65Verify کیلیت سیخومی Reserved(اختیاری، وا کاما ز یک جوڌا ابۊن) نشۊوی مهلی (اختیاری IPv4/IPv6، وا کاما ز یک جوڌا ابۊن) diff --git a/v2rayng/V2rayNG/app/src/main/res/values-fa/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-fa/strings.xml index dc8ca57320..2fe465a764 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-fa/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-fa/strings.xml @@ -80,6 +80,7 @@ کلید رمزگذاری اضافی (اختیاری) ShortID SpiderX + Mldsa65Verify کلید خصوصی Reserved (اختیاری، جدا شده با کاما) آدرس محلی (IPv4/IPv6 اختیاری، جدا شده با کاما) diff --git a/v2rayng/V2rayNG/app/src/main/res/values-ru/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-ru/strings.xml index 395471a674..126901e68a 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-ru/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-ru/strings.xml @@ -80,6 +80,7 @@ Дополнительный ключ шифрования (необязательно) ShortID SpiderX + Mldsa65Verify Закрытый ключ Reserved (необязательно, через запятую) Локальный адрес (необязательно, IPv4/IPv6 через запятую) diff --git a/v2rayng/V2rayNG/app/src/main/res/values-vi/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-vi/strings.xml index 302b78fb27..9b545d2046 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-vi/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-vi/strings.xml @@ -80,6 +80,7 @@ PreSharedKey(optional) ShortId SpiderX + Mldsa65Verify SecretKey Reserved (Không bắt buộc) Địa chỉ cục bộ (IPv4 / IPv6, phân cách bằng dấu phẩy) diff --git a/v2rayng/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml index 2a6911a221..b52e04357d 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml @@ -80,6 +80,7 @@ PreSharedKey (optional) ShortId SpiderX + Mldsa65Verify SecretKey Reserved (可选,逗号隔开) 本地地址 (可选 IPv4/IPv6,逗号隔开) diff --git a/v2rayng/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index 09824a12a2..8fda1da62d 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -80,6 +80,7 @@ PreSharedKey (optional) ShortId SpiderX + Mldsa65Verify SecretKey Reserved (可選,逗號隔開) 本機位址 (可選 IPv4/IPv6,逗號隔開) diff --git a/v2rayng/V2rayNG/app/src/main/res/values/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values/strings.xml index b9388f546d..5d70d51d99 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values/strings.xml @@ -81,6 +81,7 @@ PreSharedKey(optional) ShortId SpiderX + Mldsa65Verify SecretKey Reserved(Optional, separated by commas) Local address (optional IPv4/IPv6, separated by commas) diff --git a/xray-core/app/proxyman/inbound/inbound.go b/xray-core/app/proxyman/inbound/inbound.go index 5d64c5b2ef..8e48496594 100644 --- a/xray-core/app/proxyman/inbound/inbound.go +++ b/xray-core/app/proxyman/inbound/inbound.go @@ -17,7 +17,7 @@ import ( // Manager manages all inbound handlers. type Manager struct { access sync.RWMutex - untaggedHandler []inbound.Handler + untaggedHandlers []inbound.Handler taggedHandlers map[string]inbound.Handler running bool } @@ -47,7 +47,7 @@ func (m *Manager) AddHandler(ctx context.Context, handler inbound.Handler) error } m.taggedHandlers[tag] = handler } else { - m.untaggedHandler = append(m.untaggedHandler, handler) + m.untaggedHandlers = append(m.untaggedHandlers, handler) } if m.running { @@ -94,8 +94,8 @@ func (m *Manager) ListHandlers(ctx context.Context) []inbound.Handler { m.access.RLock() defer m.access.RUnlock() - var response []inbound.Handler - copy(m.untaggedHandler, response) + response := make([]inbound.Handler, len(m.untaggedHandlers)) + copy(response, m.untaggedHandlers) for _, v := range m.taggedHandlers { response = append(response, v) @@ -117,7 +117,7 @@ func (m *Manager) Start() error { } } - for _, handler := range m.untaggedHandler { + for _, handler := range m.untaggedHandlers { if err := handler.Start(); err != nil { return err } @@ -138,7 +138,7 @@ func (m *Manager) Close() error { errs = append(errs, err) } } - for _, handler := range m.untaggedHandler { + for _, handler := range m.untaggedHandlers { if err := handler.Close(); err != nil { errs = append(errs, err) } diff --git a/xray-core/app/proxyman/outbound/outbound.go b/xray-core/app/proxyman/outbound/outbound.go index ed47b77831..244fcffe80 100644 --- a/xray-core/app/proxyman/outbound/outbound.go +++ b/xray-core/app/proxyman/outbound/outbound.go @@ -150,8 +150,8 @@ func (m *Manager) ListHandlers(ctx context.Context) []outbound.Handler { m.access.RLock() defer m.access.RUnlock() - var response []outbound.Handler - copy(m.untaggedHandlers, response) + response := make([]outbound.Handler, len(m.untaggedHandlers)) + copy(response, m.untaggedHandlers) for _, v := range m.taggedHandler { response = append(response, v) diff --git a/xray-core/app/version/config.pb.go b/xray-core/app/version/config.pb.go new file mode 100644 index 0000000000..97607a950b --- /dev/null +++ b/xray-core/app/version/config.pb.go @@ -0,0 +1,152 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.1 +// protoc v5.28.2 +// source: app/version/config.proto + +package version + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CoreVersion string `protobuf:"bytes,1,opt,name=core_version,json=coreVersion,proto3" json:"core_version,omitempty"` + MinVersion string `protobuf:"bytes,2,opt,name=min_version,json=minVersion,proto3" json:"min_version,omitempty"` + MaxVersion string `protobuf:"bytes,3,opt,name=max_version,json=maxVersion,proto3" json:"max_version,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + mi := &file_app_version_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_app_version_config_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_app_version_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetCoreVersion() string { + if x != nil { + return x.CoreVersion + } + return "" +} + +func (x *Config) GetMinVersion() string { + if x != nil { + return x.MinVersion + } + return "" +} + +func (x *Config) GetMaxVersion() string { + if x != nil { + return x.MaxVersion + } + return "" +} + +var File_app_version_config_proto protoreflect.FileDescriptor + +var file_app_version_config_proto_rawDesc = []byte{ + 0x0a, 0x18, 0x61, 0x70, 0x70, 0x2f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x2f, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, 0x79, + 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x6d, 0x0a, 0x06, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, + 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x69, 0x6e, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x6d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, + 0x78, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x6d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x52, 0x0a, 0x14, 0x63, + 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 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, 0x61, 0x70, 0x70, 0x2f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0xaa, 0x02, 0x10, 0x58, + 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_app_version_config_proto_rawDescOnce sync.Once + file_app_version_config_proto_rawDescData = file_app_version_config_proto_rawDesc +) + +func file_app_version_config_proto_rawDescGZIP() []byte { + file_app_version_config_proto_rawDescOnce.Do(func() { + file_app_version_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_version_config_proto_rawDescData) + }) + return file_app_version_config_proto_rawDescData +} + +var file_app_version_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_app_version_config_proto_goTypes = []any{ + (*Config)(nil), // 0: xray.app.version.Config +} +var file_app_version_config_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_app_version_config_proto_init() } +func file_app_version_config_proto_init() { + if File_app_version_config_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_app_version_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_app_version_config_proto_goTypes, + DependencyIndexes: file_app_version_config_proto_depIdxs, + MessageInfos: file_app_version_config_proto_msgTypes, + }.Build() + File_app_version_config_proto = out.File + file_app_version_config_proto_rawDesc = nil + file_app_version_config_proto_goTypes = nil + file_app_version_config_proto_depIdxs = nil +} diff --git a/xray-core/app/version/config.proto b/xray-core/app/version/config.proto new file mode 100644 index 0000000000..8e804838f3 --- /dev/null +++ b/xray-core/app/version/config.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package xray.app.version; +option csharp_namespace = "Xray.App.Version"; +option go_package = "github.com/xtls/xray-core/app/version"; +option java_package = "com.xray.app.version"; +option java_multiple_files = true; + + +message Config { + string core_version = 1; + string min_version = 2; + string max_version = 3; +} diff --git a/xray-core/app/version/version.go b/xray-core/app/version/version.go new file mode 100644 index 0000000000..25d7e6ffac --- /dev/null +++ b/xray-core/app/version/version.go @@ -0,0 +1,77 @@ +package version + +import ( + "context" + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/errors" + "strconv" + "strings" +) + +type Version struct { + config *Config + ctx context.Context +} + +func New(ctx context.Context, config *Config) (*Version, error) { + if config.MinVersion != "" { + result, err := compareVersions(config.MinVersion, config.CoreVersion) + if err != nil { + return nil, err + } + if result > 0 { + return nil, errors.New("this config must be run on version ", config.MinVersion, " or higher") + } + } + if config.MaxVersion != "" { + result, err := compareVersions(config.MaxVersion, config.CoreVersion) + if err != nil { + return nil, err + } + if result < 0 { + return nil, errors.New("this config should be run on version ", config.MaxVersion, " or lower") + } + } + return &Version{config: config, ctx: ctx}, nil +} + +func compareVersions(v1, v2 string) (int, error) { + // Split version strings into components + v1Parts := strings.Split(v1, ".") + v2Parts := strings.Split(v2, ".") + + // Pad shorter versions with zeros + for len(v1Parts) < len(v2Parts) { + v1Parts = append(v1Parts, "0") + } + for len(v2Parts) < len(v1Parts) { + v2Parts = append(v2Parts, "0") + } + + // Compare each part + for i := 0; i < len(v1Parts); i++ { + // Convert parts to integers + n1, err := strconv.Atoi(v1Parts[i]) + if err != nil { + return 0, errors.New("invalid version component ", v1Parts[i], " in ", v1) + } + n2, err := strconv.Atoi(v2Parts[i]) + if err != nil { + return 0, errors.New("invalid version component ", v2Parts[i], " in ", v2) + } + + if n1 < n2 { + return -1, nil // v1 < v2 + } + if n1 > n2 { + return 1, nil // v1 > v2 + } + } + return 0, nil // v1 == v2 +} + +func init() { + common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + return New(ctx, config.(*Config)) + })) +} diff --git a/xray-core/core/core.go b/xray-core/core/core.go index bc2c045b3e..c9a9249ed9 100644 --- a/xray-core/core/core.go +++ b/xray-core/core/core.go @@ -18,8 +18,8 @@ import ( var ( Version_x byte = 25 - Version_y byte = 7 - Version_z byte = 26 + Version_y byte = 8 + Version_z byte = 3 ) var ( diff --git a/xray-core/infra/conf/transport_internet.go b/xray-core/infra/conf/transport_internet.go index 775f61e262..4a12761dd8 100644 --- a/xray-core/infra/conf/transport_internet.go +++ b/xray-core/infra/conf/transport_internet.go @@ -414,7 +414,7 @@ type TLSConfig struct { VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"` ECHServerKeys string `json:"echServerKeys"` ECHConfigList string `json:"echConfigList"` - ECHForceQuery bool `json:"echForceQuery"` + ECHForceQuery string `json:"echForceQuery"` ECHSocketSettings *SocketConfig `json:"echSockopt"` } @@ -494,6 +494,12 @@ func (c *TLSConfig) Build() (proto.Message, error) { } config.EchServerKeys = EchPrivateKey } + switch c.ECHForceQuery { + case "none", "half", "full", "": + config.EchForceQuery = c.ECHForceQuery + default: + return nil, errors.New(`invalid "echForceQuery": `, c.ECHForceQuery) + } config.EchForceQuery = c.ECHForceQuery config.EchConfigList = c.ECHConfigList if c.ECHSocketSettings != nil { diff --git a/xray-core/infra/conf/version.go b/xray-core/infra/conf/version.go new file mode 100644 index 0000000000..5fedeb08e3 --- /dev/null +++ b/xray-core/infra/conf/version.go @@ -0,0 +1,22 @@ +package conf + +import ( + "github.com/xtls/xray-core/app/version" + "github.com/xtls/xray-core/core" + "strconv" +) + +type VersionConfig struct { + MinVersion string `json:"min"` + MaxVersion string `json:"max"` +} + +func (c *VersionConfig) Build() (*version.Config, error) { + coreVersion := strconv.Itoa(int(core.Version_x)) + "." + strconv.Itoa(int(core.Version_y)) + "." + strconv.Itoa(int(core.Version_z)) + + return &version.Config{ + CoreVersion: coreVersion, + MinVersion: c.MinVersion, + MaxVersion: c.MaxVersion, + }, nil +} diff --git a/xray-core/infra/conf/xray.go b/xray-core/infra/conf/xray.go index 0e9ec3eb53..0810dd802e 100644 --- a/xray-core/infra/conf/xray.go +++ b/xray-core/infra/conf/xray.go @@ -383,6 +383,7 @@ type Config struct { FakeDNS *FakeDNSConfig `json:"fakeDns"` Observatory *ObservatoryConfig `json:"observatory"` BurstObservatory *BurstObservatoryConfig `json:"burstObservatory"` + Version *VersionConfig `json:"version"` } func (c *Config) findInboundTag(tag string) int { @@ -451,6 +452,10 @@ func (c *Config) Override(o *Config, fn string) { c.BurstObservatory = o.BurstObservatory } + if o.Version != nil { + c.Version = o.Version + } + // update the Inbound in slice if the only one in override config has same tag if len(o.InboundConfigs) > 0 { for i := range o.InboundConfigs { @@ -591,6 +596,14 @@ func (c *Config) Build() (*core.Config, error) { config.App = append(config.App, serial.ToTypedMessage(r)) } + if c.Version != nil { + r, err := c.Version.Build() + if err != nil { + return nil, errors.New("failed to build version configuration").Base(err) + } + config.App = append(config.App, serial.ToTypedMessage(r)) + } + var inbounds []InboundDetourConfig if len(c.InboundConfigs) > 0 { diff --git a/xray-core/transport/internet/tcp/hub.go b/xray-core/transport/internet/tcp/hub.go index 8747fca2ea..759dfc35a6 100644 --- a/xray-core/transport/internet/tcp/hub.go +++ b/xray-core/transport/internet/tcp/hub.go @@ -42,6 +42,9 @@ func ListenTCP(ctx context.Context, address net.Address, port net.Port, streamSe var listener net.Listener var err error if port == net.Port(0) { // unix + if !address.Family().IsDomain() { + return nil, errors.New("invalid unix listen: ", address).AtError() + } listener, err = internet.ListenSystem(ctx, &net.UnixAddr{ Name: address.Domain(), Net: "unix", diff --git a/xray-core/transport/internet/tls/config.go b/xray-core/transport/internet/tls/config.go index 4f7700ca28..90427b8d24 100644 --- a/xray-core/transport/internet/tls/config.go +++ b/xray-core/transport/internet/tls/config.go @@ -8,7 +8,6 @@ import ( "crypto/tls" "crypto/x509" "encoding/base64" - "github.com/xtls/xray-core/features/dns" "os" "slices" "strings" @@ -451,7 +450,7 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { if len(c.EchConfigList) > 0 || len(c.EchServerKeys) > 0 { err := ApplyECH(c, config) if err != nil { - if c.EchForceQuery || errors.Cause(err) != dns.ErrEmptyResponse { + if c.EchForceQuery == "full" { errors.LogError(context.Background(), err) } else { errors.LogInfo(context.Background(), err) diff --git a/xray-core/transport/internet/tls/config.pb.go b/xray-core/transport/internet/tls/config.pb.go index c30d5ef3c7..b93af6789d 100644 --- a/xray-core/transport/internet/tls/config.pb.go +++ b/xray-core/transport/internet/tls/config.pb.go @@ -220,7 +220,7 @@ type Config struct { VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"` EchServerKeys []byte `protobuf:"bytes,18,opt,name=ech_server_keys,json=echServerKeys,proto3" json:"ech_server_keys,omitempty"` EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"` - EchForceQuery bool `protobuf:"varint,20,opt,name=ech_force_query,json=echForceQuery,proto3" json:"ech_force_query,omitempty"` + EchForceQuery string `protobuf:"bytes,20,opt,name=ech_force_query,json=echForceQuery,proto3" json:"ech_force_query,omitempty"` EchSocketSettings *internet.SocketConfig `protobuf:"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3" json:"ech_socket_settings,omitempty"` } @@ -380,11 +380,11 @@ func (x *Config) GetEchConfigList() string { return "" } -func (x *Config) GetEchForceQuery() bool { +func (x *Config) GetEchForceQuery() string { if x != nil { return x.EchForceQuery } - return false + return "" } func (x *Config) GetEchSocketSettings() *internet.SocketConfig { @@ -483,7 +483,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x0a, 0x0f, 0x65, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x63, 0x68, 0x5f, 0x66, 0x6f, - 0x72, 0x63, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x72, 0x63, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x63, 0x68, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x55, 0x0a, 0x13, 0x65, 0x63, 0x68, 0x5f, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, diff --git a/xray-core/transport/internet/tls/config.proto b/xray-core/transport/internet/tls/config.proto index c990633346..6d39bc5639 100644 --- a/xray-core/transport/internet/tls/config.proto +++ b/xray-core/transport/internet/tls/config.proto @@ -98,7 +98,7 @@ message Config { string ech_config_list = 19; - bool ech_force_query = 20; + string ech_force_query = 20; SocketConfig ech_socket_settings = 21; } diff --git a/xray-core/transport/internet/tls/ech.go b/xray-core/transport/internet/tls/ech.go index 5069f06dd5..2cd07c9dc2 100644 --- a/xray-core/transport/internet/tls/ech.go +++ b/xray-core/transport/internet/tls/ech.go @@ -9,10 +9,6 @@ import ( "encoding/base64" "encoding/binary" "fmt" - utls "github.com/refraction-networking/utls" - "github.com/xtls/xray-core/common/crypto" - dns2 "github.com/xtls/xray-core/features/dns" - "golang.org/x/net/http2" "io" "net/http" "net/url" @@ -21,6 +17,11 @@ import ( "sync/atomic" "time" + utls "github.com/refraction-networking/utls" + "github.com/xtls/xray-core/common/crypto" + dns2 "github.com/xtls/xray-core/features/dns" + "golang.org/x/net/http2" + "github.com/miekg/dns" "github.com/xtls/reality" "github.com/xtls/reality/hpke" @@ -52,10 +53,18 @@ func ApplyECH(c *Config, config *tls.Config) error { // for client if len(c.EchConfigList) != 0 { + ECHForceQuery := c.EchForceQuery + switch ECHForceQuery { + case "none", "half", "full": + case "": + ECHForceQuery = "none" // default to none + default: + panic("Invalid ECHForceQuery: " + c.EchForceQuery) + } defer func() { // if failed to get ECHConfig, use an invalid one to make connection fail - if err != nil { - if c.EchForceQuery { + if err != nil || len(ECHConfig) == 0 { + if ECHForceQuery == "full" { ECHConfig = []byte{1, 1, 4, 5, 1, 4} } } @@ -106,32 +115,40 @@ type echConfigRecord struct { } var ( - // key value must be like this: "example.com|udp://1.1.1.1" + // The keys for both maps must be generated by ECHCacheKey(). GlobalECHConfigCache = utils.NewTypedSyncMap[string, *ECHConfigCache]() clientForECHDOH = utils.NewTypedSyncMap[string, *http.Client]() ) +// sockopt can be nil if not specified. +// if for clientForECHDOH, domain can be empty. +func ECHCacheKey(server, domain string, sockopt *internet.SocketConfig) string { + return server + "|" + domain + "|" + fmt.Sprintf("%p", sockopt) +} + // Update updates the ECH config for given domain and server. // this method is concurrent safe, only one update request will be sent, others get the cache. // if isLockedUpdate is true, it will not try to acquire the lock. -func (c *ECHConfigCache) Update(domain string, server string, isLockedUpdate bool, forceQuery bool, sockopt *internet.SocketConfig) ([]byte, error) { +func (c *ECHConfigCache) Update(domain string, server string, isLockedUpdate bool, forceQuery string, sockopt *internet.SocketConfig) ([]byte, error) { if !isLockedUpdate { c.UpdateLock.Lock() defer c.UpdateLock.Unlock() } // Double check cache after acquiring lock configRecord := c.configRecord.Load() - if configRecord.expire.After(time.Now()) { + if configRecord.expire.After(time.Now()) && configRecord.err == nil { errors.LogDebug(context.Background(), "Cache hit for domain after double check: ", domain) return configRecord.config, configRecord.err } // Query ECH config from DNS server errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server) echConfig, ttl, err := dnsQuery(server, domain, sockopt) - if err != nil { - if forceQuery || ttl == 0 { - return nil, err - } + // if in "full", directly return + if err != nil && forceQuery == "full" { + return nil, err + } + if ttl == 0 { + ttl = dns2.DefaultTTL } configRecord = &echConfigRecord{ config: echConfig, @@ -144,8 +161,8 @@ func (c *ECHConfigCache) Update(domain string, server string, isLockedUpdate boo // QueryRecord returns the ECH config for given domain. // If the record is not in cache or expired, it will query the DNS server and update the cache. -func QueryRecord(domain string, server string, forceQuery bool, sockopt *internet.SocketConfig) ([]byte, error) { - GlobalECHConfigCacheKey := domain + "|" + server + "|" + fmt.Sprintf("%p", sockopt) +func QueryRecord(domain string, server string, forceQuery string, sockopt *internet.SocketConfig) ([]byte, error) { + GlobalECHConfigCacheKey := ECHCacheKey(server, domain, sockopt) echConfigCache, ok := GlobalECHConfigCache.Load(GlobalECHConfigCacheKey) if !ok { echConfigCache = &ECHConfigCache{} @@ -153,7 +170,7 @@ func QueryRecord(domain string, server string, forceQuery bool, sockopt *interne echConfigCache, _ = GlobalECHConfigCache.LoadOrStore(GlobalECHConfigCacheKey, echConfigCache) } configRecord := echConfigCache.configRecord.Load() - if configRecord.expire.After(time.Now()) { + if configRecord.expire.After(time.Now()) && (configRecord.err == nil || forceQuery == "none") { errors.LogDebug(context.Background(), "Cache hit for domain: ", domain) return configRecord.config, configRecord.err } @@ -196,7 +213,7 @@ func dnsQuery(server string, domain string, sockopt *internet.SocketConfig) ([]b return nil, 0, err } var client *http.Client - serverKey := server + "|" + fmt.Sprintf("%p", sockopt) + serverKey := ECHCacheKey(server, "", sockopt) if client, _ = clientForECHDOH.Load(serverKey); client == nil { // All traffic sent by core should via xray's internet.DialSystem // This involves the behavior of some Android VPN GUI clients @@ -307,7 +324,8 @@ func dnsQuery(server string, domain string, sockopt *internet.SocketConfig) ([]b } } } - return nil, dns2.DefaultTTL, dns2.ErrEmptyResponse + // empty is valid, means no ECH config found + return nil, dns2.DefaultTTL, nil } // reference github.com/OmarTariq612/goech diff --git a/xray-core/transport/internet/tls/ech_test.go b/xray-core/transport/internet/tls/ech_test.go index 009e80a186..bdf8786884 100644 --- a/xray-core/transport/internet/tls/ech_test.go +++ b/xray-core/transport/internet/tls/ech_test.go @@ -1,7 +1,6 @@ package tls import ( - "fmt" "io" "net/http" "strings" @@ -41,7 +40,7 @@ func TestECHDial(t *testing.T) { } wg.Wait() // check cache - echConfigCache, ok := GlobalECHConfigCache.Load("encryptedsni.com|udp://1.1.1.1" + "|" + fmt.Sprintf("%p", config.EchSocketSettings)) + echConfigCache, ok := GlobalECHConfigCache.Load(ECHCacheKey("udp://1.1.1.1", "encryptedsni.com", nil)) if !ok { t.Error("ECH config cache not found") @@ -60,22 +59,12 @@ func TestECHDial(t *testing.T) { func TestECHDialFail(t *testing.T) { config := &Config{ ServerName: "cloudflare.com", - EchConfigList: "udp://1.1.1.1", + EchConfigList: "udp://127.0.0.1", + EchForceQuery: "half", } - TLSConfig := config.GetTLSConfig() - TLSConfig.NextProtos = []string{"http/1.1"} - client := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: TLSConfig, - }, - } - resp, err := client.Get("https://cloudflare.com/cdn-cgi/trace") - common.Must(err) - defer resp.Body.Close() - _, err = io.ReadAll(resp.Body) - common.Must(err) + config.GetTLSConfig() // check cache - echConfigCache, ok := GlobalECHConfigCache.Load("cloudflare.com|udp://1.1.1.1" + "|" + fmt.Sprintf("%p", config.EchSocketSettings)) + echConfigCache, ok := GlobalECHConfigCache.Load(ECHCacheKey("udp://127.0.0.1", "cloudflare.com", nil)) if !ok { t.Error("ECH config cache not found") } diff --git a/yt-dlp/yt_dlp/extractor/_extractors.py b/yt-dlp/yt_dlp/extractor/_extractors.py index 3eea0cdf6b..bb595f924b 100644 --- a/yt-dlp/yt_dlp/extractor/_extractors.py +++ b/yt-dlp/yt_dlp/extractor/_extractors.py @@ -1866,6 +1866,7 @@ from .shahid import ( from .sharepoint import SharePointIE from .sharevideos import ShareVideosEmbedIE from .shemaroome import ShemarooMeIE +from .shiey import ShieyIE from .showroomlive import ShowRoomLiveIE from .sibnet import SibnetEmbedIE from .simplecast import ( diff --git a/yt-dlp/yt_dlp/extractor/fc2.py b/yt-dlp/yt_dlp/extractor/fc2.py index f7b883155c..d343069fec 100644 --- a/yt-dlp/yt_dlp/extractor/fc2.py +++ b/yt-dlp/yt_dlp/extractor/fc2.py @@ -22,8 +22,23 @@ class FC2IE(InfoExtractor): 'md5': 'a6ebe8ebe0396518689d963774a54eb7', 'info_dict': { 'id': '20121103kUan1KHs', - 'ext': 'flv', 'title': 'Boxing again with Puff', + 'ext': 'mp4', + 'thumbnail': r're:https?://.+\.jpe?g', + }, + 'params': { + 'skip_download': 'm3u8', + }, + }, { + # Direct video url + 'url': 'https://video.fc2.com/content/20121209FP73fxDx', + 'md5': '066bdb9b3a56a97f49cbf0d0b8a75a1f', + 'info_dict': { + 'id': '20121209FP73fxDx', + 'title': 'Farewelling The Wiggles Live in Sydney Dec 8 2012', + 'ext': 'mp4', + 'thumbnail': r're:https?://.+\.jpe?g', + 'description': 'Saying goodbye to the Wiggles at their Celebration Concert in Sydney, and what a concert that was!', }, }, { 'url': 'http://video.fc2.com/en/content/20150125cEva0hDn/', @@ -104,7 +119,7 @@ class FC2IE(InfoExtractor): 'title': title, 'url': vid_url, 'ext': 'mp4', - 'protocol': 'm3u8_native', + 'protocol': 'm3u8_native' if vidplaylist.get('type') == 2 else 'https', 'description': description, 'thumbnail': thumbnail, } diff --git a/yt-dlp/yt_dlp/extractor/n1.py b/yt-dlp/yt_dlp/extractor/n1.py index e0e49161bd..b4371c299e 100644 --- a/yt-dlp/yt_dlp/extractor/n1.py +++ b/yt-dlp/yt_dlp/extractor/n1.py @@ -1,4 +1,5 @@ import re +import urllib.parse from .common import InfoExtractor from ..utils import ( @@ -38,7 +39,7 @@ class N1InfoIIE(InfoExtractor): _VALID_URL = r'https?://(?:(?:\w+\.)?n1info\.\w+|nova\.rs)/(?:[^/?#]+/){1,2}(?P[^/?#]+)' _TESTS = [{ # YouTube embedded - 'url': 'https://rs.n1info.com/sport-klub/tenis/kako-je-djokovic-propustio-istorijsku-priliku-video/', + 'url': 'https://sportklub.n1info.rs/tenis/us-open/glava-telo-igra-kako-je-novak-ispustio-istorijsku-sansu/', 'md5': '987ce6fd72acfecc453281e066b87973', 'info_dict': { 'id': 'L5Hd4hQVUpk', @@ -67,36 +68,24 @@ class N1InfoIIE(InfoExtractor): 'playable_in_embed': True, 'availability': 'public', 'live_status': 'not_live', + 'media_type': 'video', }, }, { - 'url': 'https://rs.n1info.com/vesti/djilas-los-plan-za-metro-nece-resiti-nijedan-saobracajni-problem/', + 'url': 'https://n1info.si/novice/svet/v-srbiji-samo-ta-konec-tedna-vec-kot-200-pozarov/', 'info_dict': { - 'id': 'bgmetrosot2409zta20210924174316682-n1info-rs-worldwide', + 'id': '2182656', 'ext': 'mp4', - 'title': 'Đilas: Predlog izgradnje metroa besmislen; SNS odbacuje navode', - 'upload_date': '20210924', - 'timestamp': 1632481347, - 'thumbnail': 'http://n1info.rs/wp-content/themes/ucnewsportal-n1/dist/assets/images/placeholder-image-video.jpg', - }, - 'params': { - 'skip_download': True, - }, - }, { - 'url': 'https://n1info.si/novice/slovenija/zadnji-dnevi-na-kopaliscu-ilirija-ilirija-ni-umrla-ubili-so-jo/', - 'info_dict': { - 'id': 'ljsottomazilirija3060921-n1info-si-worldwide', - 'ext': 'mp4', - 'title': 'Zadnji dnevi na kopališču Ilirija: “Ilirija ni umrla, ubili so jo”', - 'timestamp': 1632567630, - 'upload_date': '20210925', - 'thumbnail': 'https://n1info.si/wp-content/uploads/2021/09/06/1630945843-tomaz3.png', + 'title': 'V Srbiji samo ta konec tedna več kot 200 požarov', + 'timestamp': 1753611983, + 'upload_date': '20250727', + 'thumbnail': 'https://n1info.si/media/images/2025/7/1753611048_Pozar.width-1200.webp', }, 'params': { 'skip_download': True, }, }, { # Reddit embedded - 'url': 'https://ba.n1info.com/lifestyle/vucic-bolji-od-tita-ako-izgubi-ja-cu-da-crknem-jugoslavija-je-gotova/', + 'url': 'https://nova.rs/vesti/drustvo/ako-vucic-izgubi-izbore-ja-cu-da-crknem-jugoslavija-je-gotova/', 'info_dict': { 'id': '2wmfee9eycp71', 'ext': 'mp4', @@ -113,9 +102,6 @@ class N1InfoIIE(InfoExtractor): 'duration': 134, 'thumbnail': 'https://external-preview.redd.it/5nmmawSeGx60miQM3Iq-ueC9oyCLTLjjqX-qqY8uRsc.png?format=pjpg&auto=webp&s=2f973400b04d23f871b608b178e47fc01f9b8f1d', }, - 'params': { - 'skip_download': True, - }, }, { 'url': 'https://nova.rs/vesti/politika/zaklina-tatalovic-ani-brnabic-pricate-lazi-video/', 'info_dict': { @@ -126,6 +112,9 @@ class N1InfoIIE(InfoExtractor): 'timestamp': 1635861677, 'thumbnail': 'https://nova.rs/wp-content/uploads/2021/11/02/1635860298-TNJG_Ana_Brnabic_i_Zaklina_Tatalovic_100_dana_Vlade_GP.jpg', }, + 'params': { + 'skip_download': True, + }, }, { 'url': 'https://n1info.rs/vesti/cuta-biti-u-kosovskoj-mitrovici-znaci-da-te-docekaju-eksplozivnim-napravama/', 'info_dict': { @@ -155,12 +144,15 @@ class N1InfoIIE(InfoExtractor): video_id = self._match_id(url) webpage = self._download_webpage(url, video_id) - title = self._html_search_regex(r']+>(.+?)', webpage, 'title') - timestamp = unified_timestamp(self._html_search_meta('article:published_time', webpage)) + title = self._og_search_title(webpage) or self._html_extract_title(webpage) + timestamp = unified_timestamp(self._og_search_property('published_time', webpage, default=None) or self._html_search_meta('article:published_time', webpage)) plugin_data = re.findall(r'\$bp\("(?:Brid|TargetVideo)_\d+",\s(.+)\);', webpage) entries = [] if plugin_data: - site_id = self._html_search_regex(r'site:(\d+)', webpage, 'site id') + site_id = self._html_search_regex(r'site:(\d+)', webpage, 'site id', default=None) + if site_id is None: + site_id = self._search_regex( + r'partners/(\d+)', self._html_search_meta('contentUrl', webpage, fatal=True), 'site ID') for video_data in plugin_data: video_id = self._parse_json(video_data, title)['video'] entries.append({ @@ -191,10 +183,13 @@ class N1InfoIIE(InfoExtractor): for embedded_video in embedded_videos: video_data = extract_attributes(embedded_video) url = video_data.get('src') or '' - if url.startswith('https://www.youtube.com'): + hostname = urllib.parse.urlparse(url).hostname + if hostname == 'www.youtube.com': entries.append(self.url_result(url, ie='Youtube')) - elif url.startswith('https://www.redditmedia.com'): + elif hostname == 'www.redditmedia.com': entries.append(self.url_result(url, ie='Reddit')) + elif hostname == 'www.facebook.com' and 'plugins/video' in url: + entries.append(self.url_result(url, ie='FacebookPluginsVideo')) return { '_type': 'playlist', diff --git a/yt-dlp/yt_dlp/extractor/niconico.py b/yt-dlp/yt_dlp/extractor/niconico.py index a20e570e64..ecf87ea0ba 100644 --- a/yt-dlp/yt_dlp/extractor/niconico.py +++ b/yt-dlp/yt_dlp/extractor/niconico.py @@ -3,7 +3,6 @@ import functools import itertools import json import re -import time from .common import InfoExtractor, SearchInfoExtractor from ..networking.exceptions import HTTPError @@ -16,12 +15,12 @@ from ..utils import ( float_or_none, int_or_none, parse_bitrate, - parse_duration, parse_iso8601, parse_qs, parse_resolution, qualities, str_or_none, + time_seconds, truncate_string, unified_timestamp, update_url_query, @@ -38,8 +37,14 @@ from ..utils.traversal import ( class NiconicoBaseIE(InfoExtractor): + _API_BASE = 'https://nvapi.nicovideo.jp' + _BASE_URL = 'https://www.nicovideo.jp' _GEO_BYPASS = False _GEO_COUNTRIES = ['JP'] + _HEADERS = { + 'X-Frontend-ID': '6', + 'X-Frontend-Version': '0', + } _LOGIN_BASE = 'https://account.nicovideo.jp' _NETRC_MACHINE = 'niconico' @@ -99,146 +104,266 @@ class NiconicoIE(NiconicoBaseIE): IE_NAME = 'niconico' IE_DESC = 'ニコニコ動画' + _VALID_URL = r'https?://(?:(?:embed|sp|www)\.)?nicovideo\.jp/watch/(?P(?:[a-z]{2})?\d+)' + _ERROR_MAP = { + 'FORBIDDEN': { + 'ADMINISTRATOR_DELETE_VIDEO': 'Video unavailable, possibly removed by admins', + 'CHANNEL_MEMBER_ONLY': 'Channel members only', + 'DELETED_CHANNEL_VIDEO': 'Video unavailable, channel was closed', + 'DELETED_COMMUNITY_VIDEO': 'Video unavailable, community deleted or missing', + 'DEFAULT': 'Page unavailable, check the URL', + 'HARMFUL_VIDEO': 'Sensitive content, login required', + 'HIDDEN_VIDEO': 'Video unavailable, set to private', + 'NOT_ALLOWED': 'No permission', + 'PPV_VIDEO': 'PPV video, payment information required', + 'PREMIUM_ONLY': 'Premium members only', + }, + 'INVALID_PARAMETER': { + 'DEFAULT': 'Video unavailable, may not exist or was deleted', + }, + 'MAINTENANCE': { + 'DEFAULT': 'Maintenance is in progress', + }, + 'NOT_FOUND': { + 'DEFAULT': 'Video unavailable, may not exist or was deleted', + 'RIGHT_HOLDER_DELETE_VIDEO': 'Removed by rights-holder request', + }, + 'UNAUTHORIZED': { + 'DEFAULT': 'Invalid session, re-login required', + }, + 'UNKNOWN': { + 'DEFAULT': 'Failed to fetch content', + }, + } + _STATUS_MAP = { + 'needs_auth': 'PPV video, payment information required', + 'premium_only': 'Premium members only', + 'subscriber_only': 'Channel members only', + } _TESTS = [{ - 'url': 'http://www.nicovideo.jp/watch/sm22312215', + 'url': 'https://www.nicovideo.jp/watch/1173108780', 'info_dict': { - 'id': 'sm22312215', + 'id': 'sm9', 'ext': 'mp4', - 'title': 'Big Buck Bunny', - 'thumbnail': r're:https?://.*', - 'uploader': 'takuya0301', - 'uploader_id': '2698420', - 'upload_date': '20131123', - 'timestamp': int, # timestamp is unstable - 'description': '(c) copyright 2008, Blender Foundation / www.bigbuckbunny.org', - 'duration': 33, - 'view_count': int, + 'title': '新・豪血寺一族 -煩悩解放 - レッツゴー!陰陽師', + 'availability': 'public', + 'channel': '中の', + 'channel_id': '4', 'comment_count': int, + 'description': 'md5:b7f6d3e6c29552cc19fdea6a4b7dc194', + 'display_id': '1173108780', + 'duration': 320, 'genres': ['未設定'], - 'tags': [], + 'like_count': int, + 'tags': 'mincount:5', + 'thumbnail': r're:https?://img\.cdn\.nimg\.jp/s/nicovideo/thumbnails/.+', + 'timestamp': 1173108780, + 'upload_date': '20070305', + 'uploader': '中の', + 'uploader_id': '4', + 'view_count': int, }, 'params': {'skip_download': 'm3u8'}, }, { - # File downloaded with and without credentials are different, so omit - # the md5 field - 'url': 'http://www.nicovideo.jp/watch/nm14296458', + 'url': 'https://www.nicovideo.jp/watch/sm8628149', + 'info_dict': { + 'id': 'sm8628149', + 'ext': 'mp4', + 'title': '【東方】Bad Apple!!\u3000PV【影絵】', + 'availability': 'public', + 'channel': 'あにら', + 'channel_id': '10731211', + 'comment_count': int, + 'description': 'md5:1999669158cb77a45bab123c4fafe1d7', + 'display_id': 'sm8628149', + 'duration': 219, + 'genres': ['ゲーム'], + 'like_count': int, + 'tags': 'mincount:3', + 'thumbnail': r're:https?://img\.cdn\.nimg\.jp/s/nicovideo/thumbnails/.+', + 'timestamp': 1256580802, + 'upload_date': '20091026', + 'uploader': 'あにら', + 'uploader_id': '10731211', + 'view_count': int, + }, + 'params': {'skip_download': 'm3u8'}, + }, { + 'url': 'https://www.nicovideo.jp/watch/nm14296458', 'info_dict': { 'id': 'nm14296458', 'ext': 'mp4', - 'title': '【Kagamine Rin】Dance on media【Original】take2!', + 'title': '【鏡音リン】Dance on media【オリジナル】take2!', + 'availability': 'public', + 'channel': 'りょうた', + 'channel_id': '18822557', + 'comment_count': int, 'description': 'md5:9368f2b1f4178de64f2602c2f3d6cbf5', - 'thumbnail': r're:https?://.*', + 'display_id': 'nm14296458', + 'duration': 208, + 'genres': ['音楽・サウンド'], + 'like_count': int, + 'tags': 'mincount:1', + 'thumbnail': r're:https?://img\.cdn\.nimg\.jp/s/nicovideo/thumbnails/.+', + 'timestamp': 1304065916, + 'upload_date': '20110429', 'uploader': 'りょうた', 'uploader_id': '18822557', - 'upload_date': '20110429', - 'timestamp': 1304065916, - 'duration': 208.0, - 'comment_count': int, 'view_count': int, - 'genres': ['音楽・サウンド'], - 'tags': ['Translation_Request', 'Kagamine_Rin', 'Rin_Original'], }, 'params': {'skip_download': 'm3u8'}, }, { - # 'video exists but is marked as "deleted" - # md5 is unstable - 'url': 'http://www.nicovideo.jp/watch/sm10000', + 'url': 'https://www.nicovideo.jp/watch/nl1872567', 'info_dict': { - 'id': 'sm10000', - 'ext': 'unknown_video', - 'description': 'deleted', - 'title': 'ドラえもんエターナル第3話「決戦第3新東京市」<前編>', - 'thumbnail': r're:https?://.*', - 'upload_date': '20071224', - 'timestamp': int, # timestamp field has different value if logged in - 'duration': 304, - 'view_count': int, - }, - 'skip': 'Requires an account', - }, { - 'url': 'http://www.nicovideo.jp/watch/so22543406', - 'info_dict': { - 'id': '1388129933', + 'id': 'nl1872567', 'ext': 'mp4', - 'title': '【第1回】RADIOアニメロミックス ラブライブ!~のぞえりRadio Garden~', - 'description': 'md5:b27d224bb0ff53d3c8269e9f8b561cf1', - 'thumbnail': r're:https?://.*', - 'timestamp': 1388851200, - 'upload_date': '20140104', - 'uploader': 'アニメロチャンネル', - 'uploader_id': '312', - }, - 'skip': 'The viewing period of the video you were searching for has expired.', - }, { - # video not available via `getflv`; "old" HTML5 video - 'url': 'http://www.nicovideo.jp/watch/sm1151009', - 'info_dict': { - 'id': 'sm1151009', - 'ext': 'mp4', - 'title': 'マスターシステム本体内蔵のスペハリのメインテーマ(PSG版)', - 'description': 'md5:f95a3d259172667b293530cc2e41ebda', - 'thumbnail': r're:https?://.*', - 'duration': 184, - 'timestamp': 1190835883, - 'upload_date': '20070926', - 'uploader': 'denden2', - 'uploader_id': '1392194', - 'view_count': int, - 'comment_count': int, - 'genres': ['ゲーム'], - 'tags': [], - }, - 'params': {'skip_download': 'm3u8'}, - }, { - # "New" HTML5 video - 'url': 'http://www.nicovideo.jp/watch/sm31464864', - 'info_dict': { - 'id': 'sm31464864', - 'ext': 'mp4', - 'title': '新作TVアニメ「戦姫絶唱シンフォギアAXZ」PV 最高画質', - 'description': 'md5:e52974af9a96e739196b2c1ca72b5feb', - 'timestamp': 1498481660, - 'upload_date': '20170626', - 'uploader': 'no-namamae', - 'uploader_id': '40826363', - 'thumbnail': r're:https?://.*', - 'duration': 198, - 'view_count': int, - 'comment_count': int, - 'genres': ['アニメ'], - 'tags': [], - }, - 'params': {'skip_download': 'm3u8'}, - }, { - # Video without owner - 'url': 'http://www.nicovideo.jp/watch/sm18238488', - 'info_dict': { - 'id': 'sm18238488', - 'ext': 'mp4', - 'title': '【実写版】ミュータントタートルズ', - 'description': 'md5:15df8988e47a86f9e978af2064bf6d8e', - 'timestamp': 1341128008, - 'upload_date': '20120701', - 'thumbnail': r're:https?://.*', - 'duration': 5271, - 'view_count': int, + 'title': '【12/25放送分】『生対談!!ひろゆきと戀塚のニコニコを作った人 』前半', + 'availability': 'public', + 'channel': 'nicolive', + 'channel_id': '394', 'comment_count': int, + 'description': 'md5:79fc3a54cfdc93ecc2b883285149e548', + 'display_id': 'nl1872567', + 'duration': 586, 'genres': ['エンターテイメント'], - 'tags': [], + 'like_count': int, + 'tags': 'mincount:3', + 'thumbnail': r're:https?://img\.cdn\.nimg\.jp/s/nicovideo/thumbnails/.+', + 'timestamp': 1198637246, + 'upload_date': '20071226', + 'uploader': 'nicolive', + 'uploader_id': '394', + 'view_count': int, }, 'params': {'skip_download': 'm3u8'}, }, { - 'url': 'http://sp.nicovideo.jp/watch/sm28964488?ss_pos=1&cp_in=wt_tg', - 'only_matching': True, - }, { - 'note': 'a video that is only served as an ENCRYPTED HLS.', 'url': 'https://www.nicovideo.jp/watch/so38016254', - 'only_matching': True, + 'info_dict': { + 'id': 'so38016254', + 'ext': 'mp4', + 'title': '「のんのんびより のんすとっぷ」 PV', + 'availability': 'public', + 'channel': 'のんのんびより のんすとっぷ', + 'channel_id': 'ch2647028', + 'comment_count': int, + 'description': 'md5:6e2ff55b33e3645d59ef010869cde6a2', + 'display_id': 'so38016254', + 'duration': 114, + 'genres': ['アニメ'], + 'like_count': int, + 'tags': 'mincount:4', + 'thumbnail': r're:https?://img\.cdn\.nimg\.jp/s/nicovideo/thumbnails/.+', + 'timestamp': 1609146000, + 'upload_date': '20201228', + 'uploader': 'のんのんびより のんすとっぷ', + 'uploader_id': 'ch2647028', + 'view_count': int, + }, + 'params': {'skip_download': 'm3u8'}, + }, { + # smile official, but marked as user video + 'url': 'https://www.nicovideo.jp/watch/so37602536', + 'info_dict': { + 'id': 'so37602536', + 'ext': 'mp4', + 'title': '田中有紀とゆきだるまと! 限定放送アーカイブ(第12回)', + 'availability': 'subscriber_only', + 'channel': 'あみあみ16', + 'channel_id': '91072761', + 'comment_count': int, + 'description': 'md5:2ee357ec4e76d7804fb59af77107ab67', + 'display_id': 'so37602536', + 'duration': 980, + 'genres': ['エンターテイメント'], + 'like_count': int, + 'tags': 'count:4', + 'thumbnail': r're:https?://img\.cdn\.nimg\.jp/s/nicovideo/thumbnails/.+', + 'timestamp': 1601377200, + 'upload_date': '20200929', + 'uploader': 'あみあみ16', + 'uploader_id': '91072761', + 'view_count': int, + }, + 'params': {'skip_download': 'm3u8'}, + 'skip': 'Channel members only', + }, { + 'url': 'https://www.nicovideo.jp/watch/so41370536', + 'info_dict': { + 'id': 'so41370536', + 'ext': 'mp4', + 'title': 'ZUN【出演者別】超パーティー2022', + 'availability': 'premium_only', + 'channel': 'ニコニコ超会議チャンネル', + 'channel_id': 'ch2607134', + 'comment_count': int, + 'description': 'md5:5692db5ac40d3a374fc5ec182d0249c3', + 'display_id': 'so41370536', + 'duration': 63, + 'genres': ['音楽・サウンド'], + 'like_count': int, + 'tags': 'mincount:5', + 'thumbnail': r're:https?://img\.cdn\.nimg\.jp/s/nicovideo/thumbnails/.+', + 'timestamp': 1668394800, + 'upload_date': '20221114', + 'uploader': 'ニコニコ超会議チャンネル', + 'uploader_id': 'ch2607134', + 'view_count': int, + }, + 'params': {'skip_download': 'm3u8'}, + 'skip': 'Premium members only', + }, { + 'url': 'https://www.nicovideo.jp/watch/so37574174', + 'info_dict': { + 'id': 'so37574174', + 'ext': 'mp4', + 'title': 'ひぐらしのなく頃に 廿回し編\u3000第1回', + 'availability': 'subscriber_only', + 'channel': '「ひぐらしのなく頃に」オフィシャルチャンネル', + 'channel_id': 'ch2646036', + 'comment_count': int, + 'description': 'md5:5296196d51d9c0b7272b73f9a99c236a', + 'display_id': 'so37574174', + 'duration': 1931, + 'genres': ['ラジオ'], + 'like_count': int, + 'tags': 'mincount:5', + 'thumbnail': r're:https?://img\.cdn\.nimg\.jp/s/nicovideo/thumbnails/.+', + 'timestamp': 1601028000, + 'upload_date': '20200925', + 'uploader': '「ひぐらしのなく頃に」オフィシャルチャンネル', + 'uploader_id': 'ch2646036', + 'view_count': int, + }, + 'params': {'skip_download': 'm3u8'}, + 'skip': 'Channel members only', + }, { + 'url': 'https://www.nicovideo.jp/watch/so44060088', + 'info_dict': { + 'id': 'so44060088', + 'ext': 'mp4', + 'title': '松田的超英雄電波。《仮面ライダーガッチャード 放送終了記念特別番組》', + 'availability': 'subscriber_only', + 'channel': 'あみあみチャンネル', + 'channel_id': 'ch2638921', + 'comment_count': int, + 'description': 'md5:9dec5bb9a172b6d20a255ecb64fbd03e', + 'display_id': 'so44060088', + 'duration': 1881, + 'genres': ['ラジオ'], + 'like_count': int, + 'tags': 'mincount:7', + 'thumbnail': r're:https?://img\.cdn\.nimg\.jp/s/nicovideo/thumbnails/.+', + 'timestamp': 1725361200, + 'upload_date': '20240903', + 'uploader': 'あみあみチャンネル', + 'uploader_id': 'ch2638921', + 'view_count': int, + }, + 'params': {'skip_download': 'm3u8'}, + 'skip': 'Channel members only; specified continuous membership period required', }] - _VALID_URL = r'https?://(?:(?:www\.|secure\.|sp\.)?nicovideo\.jp/watch|nico\.ms)/(?P(?:[a-z]{2})?[0-9]+)' - - def _yield_dms_formats(self, api_data, video_id): + def _extract_formats(self, api_data, video_id): fmt_filter = lambda _, v: v['isAvailable'] and v['id'] videos = traverse_obj(api_data, ('media', 'domand', 'videos', fmt_filter)) audios = traverse_obj(api_data, ('media', 'domand', 'audios', fmt_filter)) @@ -247,164 +372,135 @@ class NiconicoIE(NiconicoBaseIE): if not all((videos, audios, access_key, track_id)): return - dms_m3u8_url = self._download_json( - f'https://nvapi.nicovideo.jp/v1/watch/{video_id}/access-rights/hls', video_id, - data=json.dumps({ + m3u8_url = self._download_json( + f'{self._API_BASE}/v1/watch/{video_id}/access-rights/hls', + video_id, headers={ + 'Accept': 'application/json;charset=utf-8', + 'Content-Type': 'application/json', + 'X-Access-Right-Key': access_key, + 'X-Request-With': self._BASE_URL, + **self._HEADERS, + }, query={ + 'actionTrackId': track_id, + }, data=json.dumps({ 'outputs': list(itertools.product((v['id'] for v in videos), (a['id'] for a in audios))), - }).encode(), query={'actionTrackId': track_id}, headers={ - 'x-access-right-key': access_key, - 'x-frontend-id': 6, - 'x-frontend-version': 0, - 'x-request-with': 'https://www.nicovideo.jp', - })['data']['contentUrl'] - # Getting all audio formats results in duplicate video formats which we filter out later - dms_fmts = self._extract_m3u8_formats(dms_m3u8_url, video_id, 'mp4') + }).encode(), + )['data']['contentUrl'] + raw_fmts = self._extract_m3u8_formats(m3u8_url, video_id, 'mp4') - # m3u8 extraction does not provide audio bitrates, so extract from the API data and fix - for audio_fmt in traverse_obj(dms_fmts, lambda _, v: v['vcodec'] == 'none'): - yield { - **audio_fmt, - **traverse_obj(audios, (lambda _, v: audio_fmt['format_id'].startswith(v['id']), { - 'format_id': ('id', {str}), + formats = [] + for a_fmt in traverse_obj(raw_fmts, lambda _, v: v['vcodec'] == 'none'): + formats.append({ + **a_fmt, + **traverse_obj(audios, (lambda _, v: a_fmt['format_id'].startswith(v['id']), { 'abr': ('bitRate', {float_or_none(scale=1000)}), 'asr': ('samplingRate', {int_or_none}), + 'format_id': ('id', {str}), 'quality': ('qualityLevel', {int_or_none}), - }), get_all=False), + }, any)), 'acodec': 'aac', - } + }) - # Sort before removing dupes to keep the format dicts with the lowest tbr - video_fmts = sorted((fmt for fmt in dms_fmts if fmt['vcodec'] != 'none'), key=lambda f: f['tbr']) - self._remove_duplicate_formats(video_fmts) + # Sort first, keeping the lowest-tbr formats + v_fmts = sorted((fmt for fmt in raw_fmts if fmt['vcodec'] != 'none'), key=lambda f: f['tbr']) + self._remove_duplicate_formats(v_fmts) # Calculate the true vbr/tbr by subtracting the lowest abr - min_abr = min(traverse_obj(audios, (..., 'bitRate', {float_or_none})), default=0) / 1000 - for video_fmt in video_fmts: - video_fmt['tbr'] -= min_abr - video_fmt['format_id'] = url_basename(video_fmt['url']).rpartition('.')[0] - video_fmt['quality'] = traverse_obj(videos, ( - lambda _, v: v['id'] == video_fmt['format_id'], 'qualityLevel', {int_or_none}, any)) or -1 - yield video_fmt + min_abr = traverse_obj(audios, (..., 'bitRate', {float_or_none(scale=1000)}, all, {min})) or 0 + for v_fmt in v_fmts: + v_fmt['format_id'] = url_basename(v_fmt['url']).rpartition('.')[0] + v_fmt['quality'] = traverse_obj(videos, ( + lambda _, v: v['id'] == v_fmt['format_id'], 'qualityLevel', {int_or_none}, any)) or -1 + v_fmt['tbr'] -= min_abr + formats.extend(v_fmts) - def _extract_server_response(self, webpage, video_id, fatal=True): - try: - return traverse_obj( - self._parse_json(self._html_search_meta('server-response', webpage) or '', video_id), - ('data', 'response', {dict}, {require('server response')})) - except ExtractorError: - if not fatal: - return {} - raise + return formats def _real_extract(self, url): video_id = self._match_id(url) - try: - webpage, handle = self._download_webpage_handle( - f'https://www.nicovideo.jp/watch/{video_id}', video_id, - headers=self.geo_verification_headers()) - if video_id.startswith('so'): - video_id = self._match_id(handle.url) + path = 'v3' if self.is_logged_in else 'v3_guest' + api_resp = self._download_json( + f'{self._BASE_URL}/api/watch/{path}/{video_id}', video_id, + 'Downloading API JSON', 'Unable to fetch data', headers={ + **self._HEADERS, + **self.geo_verification_headers(), + }, query={ + 'actionTrackId': f'AAAAAAAAAA_{round(time_seconds() * 1000)}', + }, expected_status=[400, 404]) - api_data = self._extract_server_response(webpage, video_id) - except ExtractorError as e: - try: - api_data = self._download_json( - f'https://www.nicovideo.jp/api/watch/v3/{video_id}', video_id, - 'Downloading API JSON', 'Unable to fetch data', query={ - '_frontendId': '6', - '_frontendVersion': '0', - 'actionTrackId': f'AAAAAAAAAA_{round(time.time() * 1000)}', - }, headers=self.geo_verification_headers())['data'] - except ExtractorError: - if not isinstance(e.cause, HTTPError): - # Raise if original exception was from _parse_json or utils.traversal.require - raise - # The webpage server response has more detailed error info than the API response - webpage = e.cause.response.read().decode('utf-8', 'replace') - reason_code = self._extract_server_response( - webpage, video_id, fatal=False).get('reasonCode') - if not reason_code: - raise - if reason_code in ('DOMESTIC_VIDEO', 'HIGH_RISK_COUNTRY_VIDEO'): - self.raise_geo_restricted(countries=self._GEO_COUNTRIES) - elif reason_code == 'HIDDEN_VIDEO': - raise ExtractorError( - 'The viewing period of this video has expired', expected=True) - elif reason_code == 'DELETED_VIDEO': - raise ExtractorError('This video has been deleted', expected=True) - raise ExtractorError(f'Niconico says: {reason_code}') + api_data = api_resp['data'] + scheduled_time = traverse_obj(api_data, ('publishScheduledAt', {str})) + status = traverse_obj(api_resp, ('meta', 'status', {int})) - availability = self._availability(**(traverse_obj(api_data, ('payment', 'video', { - 'needs_premium': ('isPremium', {bool}), + if status != 200: + err_code = traverse_obj(api_resp, ('meta', 'errorCode', {str.upper})) + reason_code = traverse_obj(api_data, ('reasonCode', {str_or_none})) + err_msg = traverse_obj(self._ERROR_MAP, (err_code, (reason_code, 'DEFAULT'), {str}, any)) + + if reason_code in ('DOMESTIC_VIDEO', 'HIGH_RISK_COUNTRY_VIDEO'): + self.raise_geo_restricted(countries=self._GEO_COUNTRIES) + elif reason_code == 'HARMFUL_VIDEO' and traverse_obj(api_data, ( + 'viewer', 'allowSensitiveContents', {bool}, + )) is False: + err_msg = 'Sensitive content, adjust display settings to watch' + elif reason_code == 'HIDDEN_VIDEO' and scheduled_time: + err_msg = f'This content is scheduled to be released at {scheduled_time}' + elif reason_code in ('CHANNEL_MEMBER_ONLY', 'HARMFUL_VIDEO', 'HIDDEN_VIDEO', 'PPV_VIDEO', 'PREMIUM_ONLY'): + self.raise_login_required(err_msg) + + if err_msg: + raise ExtractorError(err_msg, expected=True) + if status and status >= 500: + raise ExtractorError('Service temporarily unavailable', expected=True) + raise ExtractorError(f'API returned error status {status}') + + availability = self._availability(**traverse_obj(api_data, ('payment', 'video', { + 'needs_auth': (('isContinuationBenefit', 'isPpv'), {bool}, any), 'needs_subscription': ('isAdmission', {bool}), - })) or {'needs_auth': True})) + 'needs_premium': ('isPremium', {bool}), + }))) or 'public' - formats = list(self._yield_dms_formats(api_data, video_id)) - if not formats: - fail_msg = clean_html(self._html_search_regex( - r']+\bclass="fail-message"[^>]*>(?P.+?)

    ', - webpage, 'fail message', default=None, group='msg')) - if fail_msg: - self.to_screen(f'Niconico said: {fail_msg}') - if fail_msg and 'された地域と同じ地域からのみ視聴できます。' in fail_msg: - availability = None - self.raise_geo_restricted(countries=self._GEO_COUNTRIES, metadata_available=True) - elif availability == 'premium_only': - self.raise_login_required('This video requires premium', metadata_available=True) - elif availability == 'subscriber_only': - self.raise_login_required('This video is for members only', metadata_available=True) - elif availability == 'needs_auth': - self.raise_login_required(metadata_available=False) - - # Start extracting information - tags = None - if webpage: - # use og:video:tag (not logged in) - og_video_tags = re.finditer(r'', webpage) - tags = list(filter(None, (clean_html(x.group(1)) for x in og_video_tags))) - if not tags: - # use keywords and split with comma (not logged in) - kwds = self._html_search_meta('keywords', webpage, default=None) - if kwds: - tags = [x for x in kwds.split(',') if x] - if not tags: - # find in json (logged in) - tags = traverse_obj(api_data, ('tag', 'items', ..., 'name')) + formats = self._extract_formats(api_data, video_id) + err_msg = self._STATUS_MAP.get(availability) + if not formats and err_msg: + self.raise_login_required(err_msg, metadata_available=True) thumb_prefs = qualities(['url', 'middleUrl', 'largeUrl', 'player', 'ogp']) - def get_video_info(*items, get_first=True, **kwargs): - return traverse_obj(api_data, ('video', *items), get_all=not get_first, **kwargs) - return { - 'id': video_id, - '_api_data': api_data, - 'title': get_video_info(('originalTitle', 'title')) or self._og_search_title(webpage, default=None), - 'formats': formats, 'availability': availability, - 'thumbnails': [{ - 'id': key, - 'url': url, - 'ext': 'jpg', - 'preference': thumb_prefs(key), - **parse_resolution(url, lenient=True), - } for key, url in (get_video_info('thumbnail') or {}).items() if url], - 'description': clean_html(get_video_info('description')), - 'uploader': traverse_obj(api_data, ('owner', 'nickname'), ('channel', 'name'), ('community', 'name')), - 'uploader_id': str_or_none(traverse_obj(api_data, ('owner', 'id'), ('channel', 'id'), ('community', 'id'))), - 'timestamp': parse_iso8601(get_video_info('registeredAt')) or parse_iso8601( - self._html_search_meta('video:release_date', webpage, 'date published', default=None)), - 'channel': traverse_obj(api_data, ('channel', 'name'), ('community', 'name')), - 'channel_id': traverse_obj(api_data, ('channel', 'id'), ('community', 'id')), - 'view_count': int_or_none(get_video_info('count', 'view')), - 'tags': tags, - 'genre': traverse_obj(api_data, ('genre', 'label'), ('genre', 'key')), - 'comment_count': get_video_info('count', 'comment', expected_type=int), - 'duration': ( - parse_duration(self._html_search_meta('video:duration', webpage, 'video duration', default=None)) - or get_video_info('duration')), - 'webpage_url': url_or_none(url) or f'https://www.nicovideo.jp/watch/{video_id}', + 'display_id': video_id, + 'formats': formats, + 'genres': traverse_obj(api_data, ('genre', 'label', {str}, filter, all, filter)), + 'release_timestamp': parse_iso8601(scheduled_time), 'subtitles': self.extract_subtitles(video_id, api_data), + 'tags': traverse_obj(api_data, ('tag', 'items', ..., 'name', {str}, filter, all, filter)), + 'thumbnails': [{ + 'ext': 'jpg', + 'id': key, + 'preference': thumb_prefs(key), + 'url': url, + **parse_resolution(url, lenient=True), + } for key, url in traverse_obj(api_data, ( + 'video', 'thumbnail', {dict}), default={}).items()], + **traverse_obj(api_data, (('channel', 'owner'), any, { + 'channel': (('name', 'nickname'), {str}, any), + 'channel_id': ('id', {str_or_none}), + 'uploader': (('name', 'nickname'), {str}, any), + 'uploader_id': ('id', {str_or_none}), + })), + **traverse_obj(api_data, ('video', { + 'id': ('id', {str_or_none}), + 'title': ('title', {str}), + 'description': ('description', {clean_html}, filter), + 'duration': ('duration', {int_or_none}), + 'timestamp': ('registeredAt', {parse_iso8601}), + })), + **traverse_obj(api_data, ('video', 'count', { + 'comment_count': ('comment', {int_or_none}), + 'like_count': ('like', {int_or_none}), + 'view_count': ('view', {int_or_none}), + })), } def _get_subtitles(self, video_id, api_data): @@ -413,21 +509,19 @@ class NiconicoIE(NiconicoBaseIE): return danmaku = traverse_obj(self._download_json( - f'{comments_info["server"]}/v1/threads', video_id, data=json.dumps({ + f'{comments_info["server"]}/v1/threads', video_id, + 'Downloading comments', 'Failed to download comments', headers={ + 'Content-Type': 'text/plain;charset=UTF-8', + 'Origin': self._BASE_URL, + 'Referer': f'{self._BASE_URL}/', + 'X-Client-Os-Type': 'others', + **self._HEADERS, + }, data=json.dumps({ 'additionals': {}, 'params': comments_info.get('params'), 'threadKey': comments_info.get('threadKey'), }).encode(), fatal=False, - headers={ - 'Referer': 'https://www.nicovideo.jp/', - 'Origin': 'https://www.nicovideo.jp', - 'Content-Type': 'text/plain;charset=UTF-8', - 'x-client-os-type': 'others', - 'x-frontend-id': '6', - 'x-frontend-version': '0', - }, - note='Downloading comments', errnote='Failed to download comments'), - ('data', 'threads', ..., 'comments', ...)) + ), ('data', 'threads', ..., 'comments', ...)) return { 'comments': [{ diff --git a/yt-dlp/yt_dlp/extractor/roya.py b/yt-dlp/yt_dlp/extractor/roya.py index e9fe304eeb..9094808a15 100644 --- a/yt-dlp/yt_dlp/extractor/roya.py +++ b/yt-dlp/yt_dlp/extractor/roya.py @@ -3,9 +3,9 @@ from ..utils.traversal import traverse_obj class RoyaLiveIE(InfoExtractor): - _VALID_URL = r'https?://roya\.tv/live-stream/(?P\d+)' + _VALID_URL = r'https?://(?:en\.)?roya\.tv/live-stream/(?P\d+)' _TESTS = [{ - 'url': 'https://roya.tv/live-stream/1', + 'url': 'https://en.roya.tv/live-stream/1', 'info_dict': { 'id': '1', 'title': r're:Roya TV \d{4}-\d{2}-\d{2} \d{2}:\d{2}', diff --git a/yt-dlp/yt_dlp/extractor/shiey.py b/yt-dlp/yt_dlp/extractor/shiey.py new file mode 100644 index 0000000000..4e3a815fc6 --- /dev/null +++ b/yt-dlp/yt_dlp/extractor/shiey.py @@ -0,0 +1,34 @@ +import json + +from .common import InfoExtractor +from .vimeo import VimeoIE +from ..utils import extract_attributes +from ..utils.traversal import find_element, traverse_obj + + +class ShieyIE(InfoExtractor): + _VALID_URL = r'https?://(?:www\.)?shiey\.com/videos/v/(?P[^/?#]+)' + + _TESTS = [{ + 'url': 'https://www.shiey.com/videos/v/train-journey-to-edge-of-serbia-ep-2', + 'info_dict': { + 'id': '1103409448', + 'ext': 'mp4', + 'title': 'Train Journey To Edge of Serbia (Ep. 2)', + 'uploader': 'shiey', + 'uploader_url': '', + 'duration': 1364, + 'thumbnail': r're:^https?://.+', + }, + 'params': {'skip_download': True}, + 'expected_warnings': ['Failed to parse XML: not well-formed'], + }] + + def _real_extract(self, url): + video_id = self._match_id(url) + webpage = self._download_webpage(url, video_id) + oembed_html = traverse_obj(webpage, ( + {find_element(attr='data-controller', value='VideoEmbed', html=True)}, + {extract_attributes}, 'data-config-embed-video', {json.loads}, 'oembedHtml', {str})) + + return self.url_result(VimeoIE._extract_url(url, oembed_html), VimeoIE) diff --git a/yt-dlp/yt_dlp/extractor/youtube/_base.py b/yt-dlp/yt_dlp/extractor/youtube/_base.py index 0a9b510c7d..f7dadd013d 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/_base.py +++ b/yt-dlp/yt_dlp/extractor/youtube/_base.py @@ -282,6 +282,7 @@ INNERTUBE_CLIENTS = { 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 16_7_10 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1,gzip(gfe)', }, }, + 'PLAYER_PARAMS': '8AEB', 'INNERTUBE_CONTEXT_CLIENT_NAME': 2, 'GVS_PO_TOKEN_POLICY': { StreamingProtocol.HTTPS: GvsPoTokenPolicy(