Update On Mon Apr 7 20:36:19 CEST 2025

This commit is contained in:
github-action[bot]
2025-04-07 20:36:19 +02:00
parent abfa3f6b2c
commit cc70b50b0e
48 changed files with 562 additions and 245 deletions

1
.github/update.log vendored
View File

@@ -965,3 +965,4 @@ Update On Thu Apr 3 20:36:49 CEST 2025
Update On Fri Apr 4 20:36:13 CEST 2025
Update On Sat Apr 5 20:32:49 CEST 2025
Update On Sun Apr 6 20:34:52 CEST 2025
Update On Mon Apr 7 20:36:09 CEST 2025

View File

@@ -2980,9 +2980,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "flate2"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
dependencies = [
"crc32fast",
"miniz_oxide",

View File

@@ -53,7 +53,7 @@
"@csstools/normalize.css": "12.1.1",
"@emotion/babel-plugin": "11.13.5",
"@emotion/react": "11.14.0",
"@iconify/json": "2.2.323",
"@iconify/json": "2.2.324",
"@monaco-editor/react": "4.7.0",
"@tanstack/react-query": "5.71.10",
"@tanstack/react-router": "1.114.34",

View File

@@ -2,7 +2,7 @@
"manifest_version": 1,
"latest": {
"mihomo": "v1.19.4",
"mihomo_alpha": "alpha-2a08c44",
"mihomo_alpha": "alpha-9e8f4ad",
"clash_rs": "v0.7.6",
"clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.7.6-alpha+sha.5af4aa5"
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
}
},
"updated_at": "2025-04-05T22:20:47.286Z"
"updated_at": "2025-04-06T22:20:38.947Z"
}

View File

@@ -333,8 +333,8 @@ importers:
specifier: 11.14.0
version: 11.14.0(@types/react@19.0.12)(react@19.1.0)
'@iconify/json':
specifier: 2.2.323
version: 2.2.323
specifier: 2.2.324
version: 2.2.324
'@monaco-editor/react':
specifier: 4.7.0
version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -1680,8 +1680,8 @@ packages:
'@vue/compiler-sfc':
optional: true
'@iconify/json@2.2.323':
resolution: {integrity: sha512-PtRN4hK9OkT2nlEa76A5QT54E6/SOukceKQkOZv9mk44UOlaS/9fhJFNUEA+FBAXEPcnnCQb2nVui+IAn7xTSw==}
'@iconify/json@2.2.324':
resolution: {integrity: sha512-7rx2pY2NH4zn/7q04zFiiD3o7eQ8ZV0F0nf7Rkn2DyI272OWzDMw5goSULOyDdiW9sdfBLeZod/TRxEilaNNsA==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -9551,7 +9551,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@iconify/json@2.2.323':
'@iconify/json@2.2.324':
dependencies:
'@iconify/types': 2.0.0
pathe: 1.1.2
@@ -9679,7 +9679,7 @@ snapshots:
'@babel/runtime': 7.26.10
'@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@mui/types': 7.2.24(@types/react@19.0.12)
'@mui/utils': 6.4.8(@types/react@19.0.12)(react@19.1.0)
'@mui/utils': 6.4.9(@types/react@19.0.12)(react@19.1.0)
'@popperjs/core': 2.11.8
clsx: 2.1.1
prop-types: 15.8.1

View File

@@ -3,13 +3,9 @@ import { getClashInfo } from "./cmds";
import { invoke } from "@tauri-apps/api/core";
import { useLockFn } from "ahooks";
let axiosIns: AxiosInstance = null!;
/// initialize some information
/// enable force update axiosIns
export const getAxios = async (force: boolean = false) => {
if (axiosIns && !force) return axiosIns;
let instancePromise: Promise<AxiosInstance> = null!;
async function getInstancePromise() {
let server = "";
let secret = "";
@@ -26,13 +22,22 @@ export const getAxios = async (force: boolean = false) => {
if (info?.secret) secret = info?.secret;
} catch {}
axiosIns = axios.create({
const axiosIns = axios.create({
baseURL: `http://${server}`,
headers: secret ? { Authorization: `Bearer ${secret}` } : {},
timeout: 15000,
});
axiosIns.interceptors.response.use((r) => r.data);
return axiosIns;
}
/// initialize some information
/// enable force update axiosIns
export const getAxios = async (force: boolean = false) => {
if (!instancePromise || force) {
instancePromise = getInstancePromise();
}
return instancePromise;
};
/// Get Version

View File

@@ -1,4 +1,4 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
/dts-v1/;
@@ -43,38 +43,21 @@
};
};
leds {
gpio-leds {
compatible = "gpio-leds";
led_sys_red: led-0 {
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_RED>;
function = LED_FUNCTION_STATUS;
gpios = <&pio 11 GPIO_ACTIVE_LOW>;
};
led_sys_green: led-1 {
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_STATUS;
gpios = <&pio 10 GPIO_ACTIVE_LOW>;
};
};
usb_vbus: regulator-usb {
compatible = "regulator-fixed";
regulator-name = "usb-vbus";
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
gpios = <&pio 9 GPIO_ACTIVE_LOW>;
regulator-boot-on;
};
};
&uart0 {
status = "okay";
};
&watchdog {
status = "okay";
};
&eth {
@@ -87,6 +70,9 @@
reg = <0>;
phy-mode = "2500base-x";
phy-handle = <&phy1>;
nvmem-cells = <&macaddr_factory_4 2>;
nvmem-cell-names = "mac-address";
};
gmac1: mac@1 {
@@ -94,11 +80,14 @@
reg = <1>;
phy-mode = "gmii";
phy-handle = <&int_gbe_phy>;
nvmem-cells = <&macaddr_factory_4 3>;
nvmem-cell-names = "mac-address";
};
};
&mdio_bus {
phy1: phy@1 {
phy1: ethernet-phy@1 {
compatible = "ethernet-phy-ieee802.3-c45";
reg = <1>;
reset-assert-us = <100000>;
@@ -110,6 +99,49 @@
};
};
&mmc0 {
bus-width = <8>;
cap-mmc-highspeed;
max-frequency = <52000000>;
non-removable;
pinctrl-names = "default", "state_uhs";
pinctrl-0 = <&mmc0_pins_default>;
pinctrl-1 = <&mmc0_pins_uhs>;
vmmc-supply = <&reg_3p3v>;
status = "okay";
card@0 {
compatible = "mmc-card";
reg = <0>;
block {
compatible = "block-device";
partitions {
block-partition-factory {
partname = "factory";
nvmem-layout {
compatible = "fixed-layout";
#address-cells = <1>;
#size-cells = <1>;
eeprom_factory_0: eeprom@0 {
reg = <0x0 0x1000>;
};
macaddr_factory_4: macaddr@4 {
compatible = "mac-base";
reg = <0x4 0x6>;
#nvmem-cell-cells = <1>;
};
};
};
};
};
};
};
&pio {
mmc0_pins_default: mmc0-pins-default {
mux {
@@ -125,27 +157,24 @@
};
};
&uart0 {
status = "okay";
};
&usb_phy {
status = "okay";
};
&watchdog {
status = "okay";
};
&wifi {
nvmem-cells = <&eeprom_factory_0>;
nvmem-cell-names = "eeprom";
status = "okay";
};
&xhci {
status = "okay";
vbus-supply = <&usb_vbus>;
};
&wifi {
status = "okay";
};
&mmc0 {
pinctrl-names = "default", "state_uhs";
pinctrl-0 = <&mmc0_pins_default>;
pinctrl-1 = <&mmc0_pins_uhs>;
bus-width = <8>;
cap-mmc-highspeed;
max-frequency = <52000000>;
vmmc-supply = <&reg_3p3v>;
non-removable;
status = "okay";
};

View File

@@ -128,11 +128,6 @@ mediatek_setup_macs()
lan_mac=$(macaddr_add "$wan_mac" 1)
label_mac=$wan_mac
;;
huasifei,wh3000-emmc)
label_mac=$(mmc_get_mac_binary factory 0x4)
lan_mac="$(macaddr_add $label_mac 2)"
wan_mac="$(macaddr_add $label_mac 3)"
;;
imou,lc-hx3001)
lan_mac=$(mtd_get_mac_ascii u-boot-env mac)
wan_mac=$(macaddr_add "$lan_mac" 2)

View File

@@ -499,6 +499,7 @@ define Device/huasifei_wh3000-emmc
KERNEL_INITRAMFS := kernel-bin | lzma | \
fit lzma $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb with-initrd | pad-to 64k
IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata
SUPPORTED_DEVICES += huasifei,wh3000
endef
TARGET_DEVICES += huasifei_wh3000-emmc
@@ -508,11 +509,11 @@ define Device/hf_m7986r1-emmc
DEVICE_DTS := mt7986a-hf-m7986r1-emmc
DEVICE_DTS_DIR := ../dts
DEVICE_PACKAGES := kmod-usb3 kmod-mt7921e kmod-usb-net-rndis kmod-usb-serial-option f2fsck mkf2fs
SUPPORTED_DEVICES += HF-M7986R1
KERNEL := kernel-bin | lzma | fit lzma $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb
KERNEL_INITRAMFS := kernel-bin | lzma | \
fit lzma $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb with-initrd | pad-to 64k
IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata
SUPPORTED_DEVICES += HF-M7986R1
endef
TARGET_DEVICES += hf_m7986r1-emmc
@@ -526,8 +527,8 @@ define Device/hf_m7986r1-nand
PAGESIZE := 2048
KERNEL_IN_UBI := 1
DEVICE_PACKAGES := kmod-usb3 kmod-mt7921e kmod-usb-net-rndis kmod-usb-serial-option
SUPPORTED_DEVICES += HF-M7986R1
IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata
SUPPORTED_DEVICES += HF-M7986R1
endef
TARGET_DEVICES += hf_m7986r1-nand

View File

@@ -144,7 +144,7 @@ jobs:
- run: ./get-clang.sh
- run: ccache -z
- run: ./build.sh
- run: ccache -s
- run: ccache -s && ccache --evict-older-than 1d
- run: ../tests/basic.sh out/Release/naive
- name: Pack naiveproxy assets
run: |
@@ -228,7 +228,7 @@ jobs:
- run: ./get-clang.sh
- run: ccache -z
- run: ./build.sh
- run: ccache -s
- run: ccache -s && ccache --evict-older-than 1d
- run: ./get-android-sys.sh
- run: ../tests/basic.sh out/Release/naive
- name: Gradle cache
@@ -355,7 +355,7 @@ jobs:
- run: ./get-clang.sh
- run: ccache -z
- run: ./build.sh
- run: ccache -s
- run: ccache -s && ccache --evict-older-than 1d
- run: ../tests/basic.sh out/Release/naive
# No real or emulated environment is available to test this.
if: ${{ matrix.arch != 'arm64' }}
@@ -558,7 +558,7 @@ jobs:
- run: ./get-clang.sh
- run: ccache -z
- run: ./build.sh
- run: ccache -s
- run: ccache -s && ccache --evict-older-than 1d
- run: ../tests/basic.sh out/Release/naive
- name: Pack naiveproxy assets
run: |

View File

@@ -368,7 +368,7 @@ declare_args() {
stack_scan_supported =
current_cpu == "x64" || current_cpu == "x86" || current_cpu == "arm" ||
current_cpu == "arm64" || current_cpu == "riscv64" || current_cpu == "loong64"
current_cpu == "arm64" || current_cpu == "riscv64"
# We want to provide assertions that guard against inconsistent build
# args, but there is no point in having them fire if we're not building

View File

@@ -521,9 +521,6 @@ if (is_clang_or_gcc) {
} else if (current_cpu == "riscv64") {
assert(stack_scan_supported)
sources += [ "stack/asm/riscv64/push_registers_asm.cc" ]
} else if (current_cpu == "loong64") {
assert(stack_scan_supported)
sources += [ "stack/asm/loong64/push_registers_asm.cc" ]
} else {
# To support a trampoline for another arch, please refer to v8/src/heap/base.
assert(!stack_scan_supported)

View File

@@ -1,49 +0,0 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Push all callee-saved registers to get them on the stack for conservative
// stack scanning.
//
// See asm/x64/push_registers_asm.cc for why the function is not generated
// using clang.
//
// Calling convention source:
// https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html
asm(".global PAPushAllRegistersAndIterateStack \n"
".type PAPushAllRegistersAndIterateStack, %function \n"
".hidden PAPushAllRegistersAndIterateStack \n"
"PAPushAllRegistersAndIterateStack: \n"
// Push all callee-saved registers and save return address.
" addi.d $sp, $sp, -96 \n"
// Save return address.
" st.d $ra, $sp, 88 \n"
// sp is callee-saved.
" st.d $sp, $sp, 80 \n"
// s0-s9(fp) are callee-saved.
" st.d $fp, $sp, 72 \n"
" st.d $s8, $sp, 64 \n"
" st.d $s7, $sp, 56 \n"
" st.d $s6, $sp, 48 \n"
" st.d $s5, $sp, 40 \n"
" st.d $s4, $sp, 32 \n"
" st.d $s3, $sp, 24 \n"
" st.d $s2, $sp, 16 \n"
" st.d $s1, $sp, 8 \n"
" st.d $s0, $sp, 0 \n"
// Maintain frame pointer(fp is s9).
" move $fp, $sp \n"
// Pass 1st parameter (a0) unchanged (Stack*).
// Pass 2nd parameter (a1) unchanged (StackVisitor*).
// Save 3rd parameter (a2; IterateStackCallback) to a3.
" move $a3, $a2 \n"
// Pass 3rd parameter as sp (stack pointer).
" move $a2, $sp \n"
// Call the callback.
" jirl $ra, $a3, 0 \n"
// Load return address.
" ld.d $ra, $sp, 88 \n"
// Restore frame pointer.
" ld.d $fp, $sp, 72 \n"
" addi.d $sp, $sp, 96 \n"
" jr $ra \n");

View File

@@ -55,7 +55,7 @@ namespace base {
// defined as a constant.
// These constants are borrowed from glibcs (arch)/bits/pthread_stack_min.h.
#if defined(ARCH_CPU_ARM64) || defined(ARCH_CPU_LOONGARCH64)
#if defined(ARCH_CPU_ARM64)
#define PTHREAD_STACK_MIN_CONST \
(__builtin_constant_p(PTHREAD_STACK_MIN) ? PTHREAD_STACK_MIN : 131072)
#else

View File

@@ -216,15 +216,12 @@ config("default_libs") {
# linking can have run-time side effects, nothing should be listed here.
libs = []
} else if (is_linux || is_chromeos) {
# loong64 uses newer libc that subsumes libdl.so etc.
if (current_cpu != "loong64") {
libs = [
"dl",
"pthread",
"rt",
]
}
}
}
_toolchain_marker_name =

View File

@@ -488,6 +488,11 @@ def removing_unnecessary_files(install_root, arch):
ALLOWLIST = {
f"usr/lib/gcc/{gcc_triple}/{GCC_VERSION}/libgcc.a",
f"usr/lib/{TRIPLES[arch]}/libc_nonshared.a",
# https://developers.redhat.com/articles/2021/12/17/why-glibc-234-removed-libpthread
f"usr/lib/{TRIPLES[arch]}/libdl.a",
f"usr/lib/{TRIPLES[arch]}/libpthread.a",
f"usr/lib/{TRIPLES[arch]}/librt.a",
}
for file in ALLOWLIST:

View File

@@ -195,6 +195,12 @@ def test_naive_once(proxy, *args, **kwargs):
def test_naive(label, proxy, *args, **kwargs):
RETRIES = 5
result = None
if argv.target_cpu == 'arm' and not label.startswith('Default'):
# Arm tests are too slow in qemu-user
# due to https://www.openwall.com/lists/musl/2017/06/15/9
print('** SKIP TEST:', label, end='\n\n')
return
for i in range(RETRIES):
result = test_naive_once(proxy, *args, **kwargs)
if result == 'Failed to listen':

View File

@@ -2019,7 +2019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
"windows-targets 0.48.5",
]
[[package]]
@@ -3900,9 +3900,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.44.1"
version = "1.44.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
dependencies = [
"backtrace",
"bytes",

View File

@@ -654,7 +654,7 @@ if (!isEmpty(main_node)) {
urltest_nodes = [...urltest_nodes, ...filter(main_udp_urltest_nodes, (l) => !~index(urltest_nodes, l))];
} else if (dedicated_udp_node) {
const main_udp_node_cfg = uci.get_all(uciconfig, main_udp_node) || {};
if (main_node_cfg.type === 'wireguard') {
if (main_udp_node_cfg.type === 'wireguard') {
push(config.endpoints, generate_endpoint(main_udp_node_cfg));
config.endpoints[length(config.endpoints)-1].tag = 'main-udp-out';
} else {

View File

@@ -30,7 +30,7 @@ local subscribe_url = ucic:get_first(name, 'server_subscribe', 'subscribe_url',
local filter_words = ucic:get_first(name, 'server_subscribe', 'filter_words', '过期时间/剩余流量')
local save_words = ucic:get_first(name, 'server_subscribe', 'save_words', '')
-- 读取 ss_type 设置
local ss_type = ucic:get_first(name, 'server_subscribe', 'ss_type')
local ss_type = ucic:get_first(name, 'server_subscribe', 'ss_type', 'ss-rust')
-- 根据 ss_type 选择对应的程序
local ss_program = ""
if ss_type == "ss-rust" then
@@ -180,6 +180,7 @@ local function processData(szType, content)
if not isCompleteJSON(content) then
return nil
end
if szType == "hysteria2" or szType == "hy2" then
local url = URL.parse("http://" .. content)
local params = url.query
@@ -788,7 +789,7 @@ local execute = function()
if result then
-- 中文做地址的 也没有人拿中文域名搞就算中文域也有Puny Code SB 机场
if not result.server or not result.server_port or result.alias == "NULL" or check_filer(result) or result.server:match("[^0-9a-zA-Z%-_%.%s]") or cache[groupHash][result.hashkey] then
log('丢弃无效节点: ' .. result.type .. ' 节点, ' .. result.alias)
log('丢弃无效节点: ' .. result.alias)
else
-- log('成功解析: ' .. result.type ..' 节点, ' .. result.alias)
result.grouphashkey = groupHash

View File

@@ -5,12 +5,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=v2ray-core
PKG_VERSION:=5.29.3
PKG_VERSION:=5.30.0
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/v2fly/v2ray-core/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=f2a2eb4c835a99339746eaadbb1c88b4dcd022aa6d801ca44936be36f3ed027a
PKG_HASH:=8b10fc864289cb73e328d07b6d25b5e064d95e13fc5621ad4b5deb10137482b2
PKG_LICENSE:=MIT
PKG_LICENSE_FILES:=LICENSE

View File

@@ -21,22 +21,22 @@ define Download/geoip
HASH:=735786c00694313090c5d525516463836167422b132ce293873443613b496e92
endef
GEOSITE_VER:=20250405160157
GEOSITE_VER:=20250407044718
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
define Download/geosite
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
URL_FILE:=dlc.dat
FILE:=$(GEOSITE_FILE)
HASH:=bf18a50193c260b5913af089394e49ca92967a1bb416d1e8e651667985e018bc
HASH:=a35d248bdf7892fbf747d94e656e45339c1d90c6b656b5c1311d62c1f2cbaadf
endef
GEOSITE_IRAN_VER:=202503310039
GEOSITE_IRAN_VER:=202504070038
GEOSITE_IRAN_FILE:=iran.dat.$(GEOSITE_IRAN_VER)
define Download/geosite-ir
URL:=https://github.com/bootmortis/iran-hosted-domains/releases/download/$(GEOSITE_IRAN_VER)/
URL_FILE:=iran.dat
FILE:=$(GEOSITE_IRAN_FILE)
HASH:=1eb78f2dee6952b43dde65f72af44c1a064c97572ed0d72ad36fcf47ac4feafd
HASH:=ea5ed940fee6d7c872a143d160486e5d576124fc5167dfc6a8d55708281276ec
endef
define Package/v2ray-geodata/template

View File

@@ -1023,6 +1023,8 @@ public class ConfigHandler
var profileItem = await AppHandler.Instance.GetProfileItem(indexId) ?? new();
profileItem.IndexId = indexId;
if (coreType == ECoreType.Xray)
{
profileItem.Remarks = multipleLoad switch
{
EMultipleLoad.Random => ResUI.menuSetDefaultMultipleServerXrayRandom,
@@ -1031,6 +1033,11 @@ public class ConfigHandler
EMultipleLoad.LeastLoad => ResUI.menuSetDefaultMultipleServerXrayLeastLoad,
_ => ResUI.menuSetDefaultMultipleServerXrayRoundRobin,
};
}
else if (coreType == ECoreType.sing_box)
{
profileItem.Remarks = ResUI.menuSetDefaultMultipleServerSingBoxLeastPing;
}
profileItem.Address = Global.CoreMultipleLoadConfigFileName;
profileItem.ConfigType = EConfigType.Custom;
profileItem.CoreType = coreType;

View File

@@ -172,14 +172,6 @@ object AppConfig {
const val DNS_QUAD9_DOMAIN = "dns.quad9.net"
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
val DNS_CLOUDFLARE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
const val DEFAULT_PORT = 443
const val DEFAULT_SECURITY = "auto"
const val DEFAULT_LEVEL = 8
@@ -188,4 +180,27 @@ object AppConfig {
const val REALITY = "reality"
const val HEADER_TYPE_HTTP = "http"
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
val DNS_CLOUDFLARE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
val PRIVATE_IP_LIST = arrayListOf(
"0.0.0.0/8",
"10.0.0.0/8",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.2.0/24",
"192.88.99.0/24",
"192.168.0.0/16",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"224.0.0.0/4",
"240.0.0.0/4"
)
}

View File

@@ -417,6 +417,9 @@ object AngConfigManager {
if (!Utils.isValidUrl(url)) {
return 0
}
if (!Utils.isValidSubUrl(url)) {
return 0
}
Log.i(AppConfig.TAG, url)
var configText = try {
@@ -430,7 +433,7 @@ object AngConfigManager {
configText = try {
HttpUtil.getUrlContentWithUserAgent(url)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to get URL content with user agent", e)
Log.e(AppConfig.TAG, "Update subscription: Failed to get URL content with user agent", e)
""
}
}

View File

@@ -166,7 +166,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
//builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
val bypassLan = SettingsManager.routingRulesetsBypassLan()
if (bypassLan) {
resources.getStringArray(R.array.bypass_private_ip_address).forEach {
AppConfig.PRIVATE_IP_LIST.forEach {
val addr = it.split('/')
builder.addRoute(addr[0], addr[1].toInt())
}

View File

@@ -90,7 +90,7 @@ class SubEditActivity : BaseActivity() {
if (!Utils.isValidSubUrl(subItem.url)) {
toast(R.string.toast_insecure_url_protocol)
//return false
return false
}
}

View File

@@ -7,8 +7,11 @@ import com.v2ray.ang.BuildConfig
import com.v2ray.ang.util.Utils.encode
import com.v2ray.ang.util.Utils.urlDecode
import java.io.IOException
import java.net.*
import java.util.*
import java.net.HttpURLConnection
import java.net.IDN
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.URL
object HttpUtil {
@@ -20,7 +23,7 @@ object HttpUtil {
* @return The ASCII representation of the URL.
*/
fun idnToASCII(str: String): String {
val url = URI(str)
val url = URL(str)
val host = url.host
val asciiHost = IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED)
if (host != asciiHost) {

View File

@@ -20,7 +20,9 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.BuildConfig
import java.io.IOException
import java.net.InetAddress
import java.net.ServerSocket
import java.net.URI
import java.net.URLDecoder
import java.net.URLEncoder
import java.util.Locale
@@ -461,13 +463,23 @@ object Utils {
fun isValidSubUrl(value: String?): Boolean {
if (value.isNullOrEmpty()) return false
return try {
URLUtil.isHttpsUrl(value) ||
(URLUtil.isHttpUrl(value) && value.contains(LOOPBACK))
try {
if (URLUtil.isHttpsUrl(value)) return true
if (URLUtil.isHttpUrl(value)) {
if (value.contains(LOOPBACK)) return true
//Check private ip address
val uri = URI(fixIllegalUrl(value))
if (isIpAddress(uri.host)) {
AppConfig.PRIVATE_IP_LIST.forEach {
if (isIpInCidr(uri.host, it)) return true
}
}
}
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to validate subscription URL", e)
false
}
return false
}
/**
@@ -495,5 +507,49 @@ object Utils {
*/
fun isGoogleFlavor(): Boolean = BuildConfig.FLAVOR == "playstore"
/**
* Converts an InetAddress to its long representation
*
* @param ip The InetAddress to convert
* @return The long representation of the IP address
*/
private fun inetAddressToLong(ip: InetAddress): Long {
val bytes = ip.address
var result: Long = 0
for (i in bytes.indices) {
result = result shl 8 or (bytes[i].toInt() and 0xff).toLong()
}
return result
}
/**
* Check if an IP address is within a CIDR range
*
* @param ip The IP address to check
* @param cidr The CIDR notation range (e.g., "192.168.1.0/24")
* @return True if the IP is within the CIDR range, false otherwise
*/
fun isIpInCidr(ip: String, cidr: String): Boolean {
try {
if (!isIpAddress(ip)) return false
// Parse CIDR (e.g., "192.168.1.0/24")
val (cidrIp, prefixLen) = cidr.split("/")
val prefixLength = prefixLen.toInt()
// Convert IP and CIDR's IP portion to Long
val ipLong = inetAddressToLong(InetAddress.getByName(ip))
val cidrIpLong = inetAddressToLong(InetAddress.getByName(cidrIp))
// Calculate subnet mask (e.g., /24 → 0xFFFFFF00)
val mask = if (prefixLength == 0) 0L else (-1L shl (32 - prefixLength))
// Check if they're in the same subnet
return (ipLong and mask) == (cidrIpLong and mask)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to check if IP is in CIDR", e)
return false
}
}
}

View File

@@ -124,43 +124,6 @@
<item>xtls-rprx-vision-udp443</item>
</string-array>
<!-- minimum list https://serverfault.com/a/304791 -->
<string-array name="bypass_private_ip_address" translatable="false">
<item>0.0.0.0/5</item>
<item>8.0.0.0/7</item>
<item>11.0.0.0/8</item>
<item>12.0.0.0/6</item>
<item>16.0.0.0/4</item>
<item>32.0.0.0/3</item>
<item>64.0.0.0/2</item>
<item>128.0.0.0/3</item>
<item>160.0.0.0/5</item>
<item>168.0.0.0/6</item>
<item>172.0.0.0/12</item>
<item>172.32.0.0/11</item>
<item>172.64.0.0/10</item>
<item>172.128.0.0/9</item>
<item>173.0.0.0/8</item>
<item>174.0.0.0/7</item>
<item>176.0.0.0/4</item>
<item>192.0.0.0/9</item>
<item>192.128.0.0/11</item>
<item>192.160.0.0/13</item>
<item>192.169.0.0/16</item>
<item>192.170.0.0/15</item>
<item>192.172.0.0/14</item>
<item>192.176.0.0/12</item>
<item>192.192.0.0/10</item>
<item>193.0.0.0/8</item>
<item>194.0.0.0/7</item>
<item>196.0.0.0/6</item>
<item>200.0.0.0/5</item>
<item>208.0.0.0/4</item>
<item>240.0.0.0/4</item>
</string-array>
<string-array name="language_select" translatable="false">
<item>auto</item>
<item>English</item>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<base-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates

View File

@@ -0,0 +1,41 @@
package com.v2ray.ang
import com.v2ray.ang.util.HttpUtil
import org.junit.Assert.assertEquals
import org.junit.Test
class HttpUtilTest {
@Test
fun testIdnToASCII() {
// Regular URL remains unchanged
val regularUrl = "https://example.com/path"
assertEquals(regularUrl, HttpUtil.idnToASCII(regularUrl))
// Non-ASCII URL converts to ASCII (Punycode)
val nonAsciiUrl = "https://例子.测试/path"
val expectedNonAscii = "https://xn--fsqu00a.xn--0zwm56d/path"
assertEquals(expectedNonAscii, HttpUtil.idnToASCII(nonAsciiUrl))
// Mixed URL only converts the host part
val mixedUrl = "https://例子.com/测试"
val expectedMixed = "https://xn--fsqu00a.com/测试"
assertEquals(expectedMixed, HttpUtil.idnToASCII(mixedUrl))
// URL with Basic Authentication using regular domain
val basicAuthUrl = "https://user:password@example.com/path"
assertEquals(basicAuthUrl, HttpUtil.idnToASCII(basicAuthUrl))
// URL with Basic Authentication using non-ASCII domain
val basicAuthNonAscii = "https://user:password@例子.测试/path"
val expectedBasicAuthNonAscii = "https://user:password@xn--fsqu00a.xn--0zwm56d/path"
assertEquals(expectedBasicAuthNonAscii, HttpUtil.idnToASCII(basicAuthNonAscii))
// URL with non-ASCII username and password
val nonAsciiAuth = "https://用户:密码@example.com/path"
// Basic auth credentials should remain unchanged as they're percent-encoded separately
assertEquals(nonAsciiAuth, HttpUtil.idnToASCII(nonAsciiAuth))
}
}

View File

@@ -11,7 +11,7 @@ import org.junit.Test
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class AngUnitTest {
class UtilsTest {
@Test
fun test_parseInt() {
@@ -45,4 +45,18 @@ class AngUnitTest {
assertTrue(Utils.isIpAddress("240e:1234:abcd:12::/64"))
}
@Test
fun test_IsIpInCidr() {
assertTrue(Utils.isIpInCidr("192.168.1.1", "192.168.1.0/24"))
assertTrue(Utils.isIpInCidr("192.168.1.254", "192.168.1.0/24"))
assertFalse(Utils.isIpInCidr("192.168.2.1", "192.168.1.0/24"))
assertTrue(Utils.isIpInCidr("10.0.0.0", "10.0.0.0/8"))
assertTrue(Utils.isIpInCidr("10.255.255.255", "10.0.0.0/8"))
assertFalse(Utils.isIpInCidr("11.0.0.0", "10.0.0.0/8"))
assertFalse(Utils.isIpInCidr("invalid-ip", "192.168.1.0/24"))
assertFalse(Utils.isIpInCidr("192.168.1.1", "invalid-cidr"))
}
}

View File

@@ -9,7 +9,7 @@ require (
github.com/golang/mock v1.7.0-rc.1
github.com/google/go-cmp v0.7.0
github.com/gorilla/websocket v1.5.3
github.com/miekg/dns v1.1.64
github.com/miekg/dns v1.1.65
github.com/pelletier/go-toml v1.9.5
github.com/pires/go-proxyproto v0.8.0
github.com/quic-go/quic-go v0.50.1
@@ -22,12 +22,12 @@ require (
github.com/vishvananda/netlink v1.3.0
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.36.0
golang.org/x/crypto v0.37.0
golang.org/x/net v0.38.0
golang.org/x/sync v0.12.0
golang.org/x/sys v0.31.0
golang.org/x/sync v0.13.0
golang.org/x/sys v0.32.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
google.golang.org/grpc v1.71.0
google.golang.org/grpc v1.71.1
google.golang.org/protobuf v1.36.6
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0
h12.io/socks v1.0.3
@@ -51,7 +51,7 @@ require (
go.uber.org/mock v0.5.0 // indirect
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.30.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect

View File

@@ -38,8 +38,8 @@ github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0N
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ=
github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc=
github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
@@ -97,8 +97,8 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
@@ -111,8 +111,8 @@ golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -121,14 +121,14 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -145,8 +145,8 @@ golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uI
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

View File

@@ -8,11 +8,13 @@ import (
type Duration int64
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (d *Duration) MarshalJSON() ([]byte, error) {
dr := time.Duration(*d)
return json.Marshal(dr.String())
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {

View File

@@ -23,6 +23,7 @@ func (v StringList) Len() int {
return len(v)
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *StringList) UnmarshalJSON(data []byte) error {
var strarray []string
if err := json.Unmarshal(data, &strarray); err == nil {
@@ -43,10 +44,12 @@ type Address struct {
net.Address
}
func (v Address) MarshalJSON() ([]byte, error) {
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (v *Address) MarshalJSON() ([]byte, error) {
return json.Marshal(v.Address.String())
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *Address) UnmarshalJSON(data []byte) error {
var rawStr string
if err := json.Unmarshal(data, &rawStr); err != nil {
@@ -81,6 +84,7 @@ func (v Network) Build() net.Network {
type NetworkList []Network
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *NetworkList) UnmarshalJSON(data []byte) error {
var strarray []Network
if err := json.Unmarshal(data, &strarray); err == nil {
@@ -169,6 +173,19 @@ func (v *PortRange) Build() *net.PortRange {
}
}
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (v *PortRange) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())
}
func (port *PortRange) String() string {
if port.From == port.To {
return strconv.Itoa(int(port.From))
} else {
return fmt.Sprintf("%d-%d", port.From, port.To)
}
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *PortRange) UnmarshalJSON(data []byte) error {
port, err := parseIntPort(data)
@@ -203,20 +220,21 @@ func (list *PortList) Build() *net.PortList {
return portList
}
func (v PortList) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (v *PortList) MarshalJSON() ([]byte, error) {
portStr := v.String()
port, err := strconv.Atoi(portStr)
if err == nil {
return json.Marshal(port)
} else {
return json.Marshal(portStr)
}
}
func (v PortList) String() string {
ports := []string{}
for _, port := range v.Range {
if port.From == port.To {
p := strconv.Itoa(int(port.From))
ports = append(ports, p)
} else {
p := fmt.Sprintf("%d-%d", port.From, port.To)
ports = append(ports, p)
}
ports = append(ports, port.String())
}
return strings.Join(ports, ",")
}
@@ -277,7 +295,8 @@ type Int32Range struct {
To int32
}
func (v Int32Range) MarshalJSON() ([]byte, error) {
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (v *Int32Range) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())
}
@@ -289,6 +308,7 @@ func (v Int32Range) String() string {
}
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *Int32Range) UnmarshalJSON(data []byte) error {
defer v.ensureOrder()
var str string

View File

@@ -25,6 +25,7 @@ type NameServerConfig struct {
TimeoutMs uint64 `json:"timeoutMs"`
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
var address Address
if err := json.Unmarshal(data, &address); err == nil {
@@ -163,6 +164,18 @@ type HostAddress struct {
addrs []*Address
}
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (h *HostAddress) MarshalJSON() ([]byte, error) {
if (h.addr != nil) != (h.addrs != nil) {
if h.addr != nil {
return json.Marshal(h.addr)
} else if h.addrs != nil {
return json.Marshal(h.addrs)
}
}
return nil, errors.New("unexpected config state")
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (h *HostAddress) UnmarshalJSON(data []byte) error {
addr := new(Address)
@@ -208,6 +221,11 @@ func getHostMapping(ha *HostAddress) *dns.Config_HostMapping {
}
}
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (m *HostsWrapper) MarshalJSON() ([]byte, error) {
return json.Marshal(m.Hosts)
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (m *HostsWrapper) UnmarshalJSON(data []byte) error {
hosts := make(map[string]*HostAddress)

View File

@@ -20,6 +20,18 @@ type FakeDNSConfig struct {
pools []*FakeDNSPoolElementConfig
}
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (f *FakeDNSConfig) MarshalJSON() ([]byte, error) {
if (f.pool != nil) != (f.pools != nil) {
if f.pool != nil {
return json.Marshal(f.pool)
} else if f.pools != nil {
return json.Marshal(f.pools)
}
}
return nil, errors.New("unexpected config state")
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (f *FakeDNSConfig) UnmarshalJSON(data []byte) error {
var pool FakeDNSPoolElementConfig

View File

@@ -13,9 +13,6 @@ const (
TCP_FASTOPEN = 15
IP_UNICAST_IF = 31
IPV6_UNICAST_IF = 31
IP_MULTICAST_IF = 9
IPV6_MULTICAST_IF = 9
IPV6_V6ONLY = 27
)
func setTFO(fd syscall.Handle, tfo int) error {
@@ -44,14 +41,14 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx)); err != nil {
return errors.New("failed to set IP_UNICAST_IF").Base(err)
}
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, IP_MULTICAST_IF, int(idx)); err != nil {
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF, int(idx)); err != nil {
return errors.New("failed to set IP_MULTICAST_IF").Base(err)
}
} else {
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, inf.Index); err != nil {
return errors.New("failed to set IPV6_UNICAST_IF").Base(err)
}
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, IPV6_MULTICAST_IF, inf.Index); err != nil {
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, syscall.IPV6_MULTICAST_IF, inf.Index); err != nil {
return errors.New("failed to set IPV6_MULTICAST_IF").Base(err)
}
}
@@ -92,7 +89,7 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig)
}
if config.V6Only {
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, IPV6_V6ONLY, 1); err != nil {
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 1); err != nil {
return errors.New("failed to set IPV6_V6ONLY").Base(err)
}
}

View File

@@ -659,6 +659,8 @@ class TestUtil(unittest.TestCase):
self.assertEqual(url_or_none('mms://foo.de'), 'mms://foo.de')
self.assertEqual(url_or_none('rtspu://foo.de'), 'rtspu://foo.de')
self.assertEqual(url_or_none('ftps://foo.de'), 'ftps://foo.de')
self.assertEqual(url_or_none('ws://foo.de'), 'ws://foo.de')
self.assertEqual(url_or_none('wss://foo.de'), 'wss://foo.de')
def test_parse_age_limit(self):
self.assertEqual(parse_age_limit(None), None)

View File

@@ -85,6 +85,7 @@ class NiconicoLiveFD(FileDownloader):
'quality': live_quality,
'protocol': 'hls+fmp4',
'latency': live_latency,
'accessRightMethod': 'single_cookie',
'chasePlay': False,
},
'room': {

View File

@@ -903,6 +903,7 @@ from .ivi import (
IviIE,
)
from .ivideon import IvideonIE
from .ivoox import IvooxIE
from .iwara import (
IwaraIE,
IwaraPlaylistIE,
@@ -960,7 +961,10 @@ from .kick import (
)
from .kicker import KickerIE
from .kickstarter import KickStarterIE
from .kika import KikaIE
from .kika import (
KikaIE,
KikaPlaylistIE,
)
from .kinja import KinjaEmbedIE
from .kinopoisk import KinoPoiskIE
from .kommunetv import KommunetvIE
@@ -1061,6 +1065,7 @@ from .loom import (
from .lovehomeporn import LoveHomePornIE
from .lrt import (
LRTVODIE,
LRTRadioIE,
LRTStreamIE,
)
from .lsm import (

View File

@@ -0,0 +1,78 @@
from .common import InfoExtractor
from ..utils import int_or_none, parse_iso8601, url_or_none, urljoin
from ..utils.traversal import traverse_obj
class IvooxIE(InfoExtractor):
_VALID_URL = (
r'https?://(?:www\.)?ivoox\.com/(?:\w{2}/)?[^/?#]+_rf_(?P<id>[0-9]+)_1\.html',
r'https?://go\.ivoox\.com/rf/(?P<id>[0-9]+)',
)
_TESTS = [{
'url': 'https://www.ivoox.com/dex-08x30-rostros-del-mal-los-asesinos-en-audios-mp3_rf_143594959_1.html',
'md5': '993f712de5b7d552459fc66aa3726885',
'info_dict': {
'id': '143594959',
'ext': 'mp3',
'timestamp': 1742731200,
'channel': 'DIAS EXTRAÑOS con Santiago Camacho',
'title': 'DEx 08x30 Rostros del mal: Los asesinos en serie que aterrorizaron España',
'description': 'md5:eae8b4b9740d0216d3871390b056bb08',
'uploader': 'Santiago Camacho',
'thumbnail': 'https://static-1.ivoox.com/audios/c/d/5/2/cd52f46783fe735000c33a803dce2554_XXL.jpg',
'upload_date': '20250323',
'episode': 'DEx 08x30 Rostros del mal: Los asesinos en serie que aterrorizaron España',
'duration': 11837,
'tags': ['españa', 'asesinos en serie', 'arropiero', 'historia criminal', 'mataviejas'],
},
}, {
'url': 'https://go.ivoox.com/rf/143594959',
'only_matching': True,
}, {
'url': 'https://www.ivoox.com/en/campodelgas-28-03-2025-audios-mp3_rf_144036942_1.html',
'only_matching': True,
}]
def _real_extract(self, url):
media_id = self._match_id(url)
webpage = self._download_webpage(url, media_id, fatal=False)
data = self._search_nuxt_data(
webpage, media_id, fatal=False, traverse=('data', 0, 'data', 'audio'))
direct_download = self._download_json(
f'https://vcore-web.ivoox.com/v1/public/audios/{media_id}/download-url', media_id, fatal=False,
note='Fetching direct download link', headers={'Referer': url})
download_paths = {
*traverse_obj(direct_download, ('data', 'downloadUrl', {str}, filter, all)),
*traverse_obj(data, (('downloadUrl', 'mediaUrl'), {str}, filter)),
}
formats = []
for path in download_paths:
formats.append({
'url': urljoin('https://ivoox.com', path),
'http_headers': {'Referer': url},
})
return {
'id': media_id,
'formats': formats,
'uploader': self._html_search_regex(r'data-prm-author="([^"]+)"', webpage, 'author', default=None),
'timestamp': parse_iso8601(
self._html_search_regex(r'data-prm-pubdate="([^"]+)"', webpage, 'timestamp', default=None)),
'channel': self._html_search_regex(r'data-prm-podname="([^"]+)"', webpage, 'channel', default=None),
'title': self._html_search_regex(r'data-prm-title="([^"]+)"', webpage, 'title', default=None),
'thumbnail': self._og_search_thumbnail(webpage, default=None),
'description': self._og_search_description(webpage, default=None),
**self._search_json_ld(webpage, media_id, default={}),
**traverse_obj(data, {
'title': ('title', {str}),
'description': ('description', {str}),
'thumbnail': ('image', {url_or_none}),
'timestamp': ('uploadDate', {parse_iso8601(delimiter=' ')}),
'duration': ('duration', {int_or_none}),
'tags': ('tags', ..., 'name', {str}),
}),
}

View File

@@ -1,3 +1,5 @@
import itertools
from .common import InfoExtractor
from ..utils import (
determine_ext,
@@ -124,3 +126,43 @@ class KikaIE(InfoExtractor):
'vbr': ('bitrateVideo', {int_or_none}, {lambda x: None if x == -1 else x}),
}),
}
class KikaPlaylistIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?kika\.de/[\w-]+/(?P<id>[a-z-]+\d+)'
_TESTS = [{
'url': 'https://www.kika.de/logo/logo-die-welt-und-ich-562',
'info_dict': {
'id': 'logo-die-welt-und-ich-562',
'title': 'logo!',
'description': 'md5:7b9d7f65561b82fa512f2cfb553c397d',
},
'playlist_count': 100,
}]
def _entries(self, playlist_url, playlist_id):
for page in itertools.count(1):
data = self._download_json(playlist_url, playlist_id, note=f'Downloading page {page}')
for item in traverse_obj(data, ('content', lambda _, v: url_or_none(v['api']['url']))):
yield self.url_result(
item['api']['url'], ie=KikaIE,
**traverse_obj(item, {
'id': ('id', {str}),
'title': ('title', {str}),
'duration': ('duration', {int_or_none}),
'timestamp': ('date', {parse_iso8601}),
}))
playlist_url = traverse_obj(data, ('links', 'next', {url_or_none}))
if not playlist_url:
break
def _real_extract(self, url):
playlist_id = self._match_id(url)
brand_data = self._download_json(
f'https://www.kika.de/_next-api/proxy/v1/brands/{playlist_id}', playlist_id)
return self.playlist_result(
self._entries(brand_data['videoSubchannel']['videosPageUrl'], playlist_id),
playlist_id, title=brand_data.get('title'), description=brand_data.get('description'))

View File

@@ -2,8 +2,11 @@ from .common import InfoExtractor
from ..utils import (
clean_html,
merge_dicts,
str_or_none,
traverse_obj,
unified_timestamp,
url_or_none,
urljoin,
)
@@ -80,7 +83,7 @@ class LRTVODIE(LRTBaseIE):
}]
def _real_extract(self, url):
path, video_id = self._match_valid_url(url).groups()
path, video_id = self._match_valid_url(url).group('path', 'id')
webpage = self._download_webpage(url, video_id)
media_url = self._extract_js_var(webpage, 'main_url', path)
@@ -106,3 +109,42 @@ class LRTVODIE(LRTBaseIE):
}
return merge_dicts(clean_info, jw_data, json_ld_data)
class LRTRadioIE(LRTBaseIE):
_VALID_URL = r'https?://(?:www\.)?lrt\.lt/radioteka/irasas/(?P<id>\d+)/(?P<path>[^?#/]+)'
_TESTS = [{
# m3u8 download
'url': 'https://www.lrt.lt/radioteka/irasas/2000359728/nemarios-eiles-apie-pragarus-ir-skaistyklas-su-aiste-kiltinaviciute',
'info_dict': {
'id': '2000359728',
'ext': 'm4a',
'title': 'Nemarios eilės: apie pragarus ir skaistyklas su Aiste Kiltinavičiūte',
'description': 'md5:5eee9a0e86a55bf547bd67596204625d',
'timestamp': 1726143120,
'upload_date': '20240912',
'tags': 'count:5',
'thumbnail': r're:https?://.+/.+\.jpe?g',
'categories': ['Daiktiniai įrodymai'],
},
}, {
'url': 'https://www.lrt.lt/radioteka/irasas/2000304654/vakaras-su-knyga-svetlana-aleksijevic-cernobylio-malda-v-dalis?season=%2Fmediateka%2Faudio%2Fvakaras-su-knyga%2F2023',
'only_matching': True,
}]
def _real_extract(self, url):
video_id, path = self._match_valid_url(url).group('id', 'path')
media = self._download_json(
'https://www.lrt.lt/radioteka/api/media', video_id,
query={'url': f'/mediateka/irasas/{video_id}/{path}'})
return traverse_obj(media, {
'id': ('id', {int}, {str_or_none}),
'title': ('title', {str}),
'tags': ('tags', ..., 'name', {str}),
'categories': ('playlist_item', 'category', {str}, filter, all, filter),
'description': ('content', {clean_html}, {str}),
'timestamp': ('date', {lambda x: x.replace('.', '/')}, {unified_timestamp}),
'thumbnail': ('playlist_item', 'image', {urljoin('https://www.lrt.lt')}),
'formats': ('playlist_item', 'file', {lambda x: self._extract_m3u8_formats(x, video_id)}),
})

View File

@@ -27,6 +27,7 @@ from ..utils import (
traverse_obj,
try_get,
unescapeHTML,
unified_timestamp,
update_url_query,
url_basename,
url_or_none,
@@ -985,6 +986,7 @@ class NiconicoLiveIE(InfoExtractor):
'quality': 'abr',
'protocol': 'hls+fmp4',
'latency': latency,
'accessRightMethod': 'single_cookie',
'chasePlay': False,
},
'room': {
@@ -1005,6 +1007,7 @@ class NiconicoLiveIE(InfoExtractor):
if data.get('type') == 'stream':
m3u8_url = data['data']['uri']
qualities = data['data']['availableQualities']
cookies = data['data']['cookies']
break
elif data.get('type') == 'disconnect':
self.write_debug(recv)
@@ -1043,6 +1046,11 @@ class NiconicoLiveIE(InfoExtractor):
**res,
})
for cookie in cookies:
self._set_cookie(
cookie['domain'], cookie['name'], cookie['value'],
expire_time=unified_timestamp(cookie['expires']), path=cookie['path'], secure=cookie['secure'])
formats = self._extract_m3u8_formats(m3u8_url, video_id, ext='mp4', live=True)
for fmt, q in zip(formats, reversed(qualities[1:])):
fmt.update({

View File

@@ -2044,7 +2044,7 @@ def url_or_none(url):
if not url or not isinstance(url, str):
return None
url = url.strip()
return url if re.match(r'(?:(?:https?|rt(?:m(?:pt?[es]?|fp)|sp[su]?)|mms|ftps?):)?//', url) else None
return url if re.match(r'(?:(?:https?|rt(?:m(?:pt?[es]?|fp)|sp[su]?)|mms|ftps?|wss?):)?//', url) else None
def strftime_or_none(timestamp, date_format='%Y%m%d', default=None):